编辑距离与最长公共子序列总结
2015-12-04 16:27
204 查看
前言:
其实编辑距离和最长公共子序列是对同一个问题的描述,都能够显示出两个字符串之间的“相似度”,即它们的雷同程度。而子序列与字串的区别在于字串是连续的,子序列可以不连续,只要下标以此递增就行。
编辑距离:
Problem description:
设A 和B 是2 个字符串。要用最少的字符操作将字符串A 转换为字符串B。这里所说的字符操作包括 (1)删除一个字符; (2)插入一个字符; (3)将一个字符改为另一个字符。将字符串A变换为字符串B 所用的最少字符操作数称为字符串A到B 的编辑距离,记为 d(A,B)。试设计一个有效算法,对任给的2 个字符串A和B,计算出它们的编辑距离d(A,B)。
Input
输入的第一行是字符串A,文件的第二行是字符串B。
Output
程序运行结束时,将编辑距离d(A,B)输出。
Sample Input
fxpimu
xwrs
Sample Output
5
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
int _Min(int a,int b,int c)
{
int min=a;
if (b <min)
min=b;
if(c <min)
min=c;
return min;
}
int ComputeDistance(char s[],char t[])
{
int n = strlen(s);
int m = strlen(t);
//int d[][] = new int[n + 1, m + 1]; // matrix
int **d = (int **)malloc((n+1) * sizeof(int *)); //如何用malloc返回值强制转化为二重指针
for(int i=0; i<=n; ++i)
{
d[i] = (int *)malloc((m+1) * sizeof(int));
}
// Step 1
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
// Step 2
for (int i = 0; i <= n; i++)
{
d[i][0] =i;
}
for (int j = 0; j <= m; d[0][j] = j++)
{
d[0][j] =j;
}
// Step 3
for (int i = 1; i <= n; i++)
{
//Step 4
for (int j = 1; j <= m; j++)
{
// Step 5
int cost = (t[j-1] == s[i-1]) ? 0 : 1;
// Step 6
d[i][j] = _Min(d[i-1][j]+1, d[i][j-1]+1,d[i-1][j-1]+cost);
}
}
// Step 7
return d
[m];
}
int main(int argc, char *argv[])
{
char a[9999];
char b[9999];
printf("请输入字符串1\n");
scanf("%s",&a);
printf("请输入字符串2\n");
scanf("%s",&b);
int result= ComputeDistance(a,b);
printf("%d\n",result);
system("PAUSE");
return 0;
}
////////////////////
Refrence : Dynamic Programming Algorithm(DPA) for Edit-Distance
编辑距离
关于两个字符串s1,s2的差别,可以通过计算他们的最小编辑距离来决定。
所谓的编辑距离: 让s1和s2变成相同字符串需要下面操作的最小次数。
1. 把某个字符ch1变成ch2
2. 删除某个字符
3. 插入某个字符
例如 s1 =
“12433”和s2=”1233”;
则可以通过在s2中间插入4得到12433与s1一致。
即 d(s1,s2) = 1 (进行了一次插入操作)
编辑距离的性质
计算两个字符串s1+ch1, s2+ch2的编辑距离有这样的性质:
1. d(s1,””) = d(“”,s1) =|s1| d(“ch1”,”ch2”)
=ch1 == ch2 ? 0 : 1;
2. d(s1+ch1,s2+ch2) = min( d(s1,s2)+ ch1==ch2 ? 0 : 1 ,
d(s1+ch1,s2),
d(s1,s2+ch2) );
第一个性质是显然的。
第二个性质: 由于我们定义的三个操作来作为编辑距离的一种衡量方法。
于是对ch1,ch2可能的操作只有
1. 把ch1变成ch2
2. s1+ch1后删除ch1 d =(1+d(s1,s2+ch2))
3. s1+ch1后插入ch2 d =(1 + d(s1+ch1,s2))
对于2和3的操作可以等价于:
_2. s2+ch2后添加ch1 d=(1+d(s1,s2+ch2))
_3. s2+ch2后删除ch2 d=(1+d(s1+ch1,s2))
因此可以得到计算编辑距离的性质2。
复杂度分析
从上面性质2可以看出计算过程呈现这样的一种结构(假设各个层用当前计算的串长度标记,并假设两个串长度都为
n )
可以看到,该问题的复杂度为指数级别 3的 n次方,对于较长的串,时间上是无法让人忍受的。
分析: 在上面的结构中,我们发现多次出现了(n-1,n-1),
(n-1,n-2)……。换句话说该结构具有重叠子问题。再加上前面性质2所具有的最优子结构。符合动态规划算法基本要素。因此可以使用动态规划算法把复杂度降低到多项式级别。
动态规划求解
首先为了避免重复计算子问题,添加两个辅助数组。
一. 保存子问题结果。
M[ |s1| ,|s2| ] , 其中M[ i , j ]表示子串 s1(0->i)与
s2(0->j)的编辑距离
二. 保存字符之间的编辑距离.
E[ |s1|, |s2| ] , 其中 E[ i, j ] = s[i] = s[j] ?0 : 1
三. 新的计算表达式
根据性质1得到
M[ 0,0] = 0;
M[ s1i, 0 ] = |s1i|;
M[ 0, s2j ] = |s2j|;
根据性质2得到
M[ i, j ] = min( m[i-1,j-1] + E[ i, j ] ,
m[i, j-1] ,
m[i-1,j] );
复杂度
从新的计算式看出,计算过程为
i=1 -> |s1|
j=1 -> |s2|
M[i][j] =
……
因此复杂度为 O( |s1| * |s2| ),如果假设他们的长度都为n,则复杂度为
O(n^2)
解题代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int fun(char sa[],char sb[])
{
intlen_a=strlen(sa),len_b=strlen(sb);
chararry[100][100]={0};
inti,j;
inta,b,c,t;
for(i=0;i<=len_a;i++)
{
for(j=0;j<=len_b;j++)
{
if(i==0)arry[i][j]=j;
elseif(j==0)arry[i][j]=i;
else
{
a=arry[i-1][j]+1;
b=arry[i][j-1]+1;
if(sa[i-1]==sb[j-1])c=arry[i-1][j-1];
else c=arry[i-1][j-1]+1;
t=b<c?b:c;
arry[i][j]=a<t?a:t;
};
}
}
return arry[i-1][j-1];
}
int main()
{
intline,i;
intans[100];
char sa[10000],sb[10000],e;
scanf("%d",&line);
e=getchar();
for(i=0;i<line;i++)
{
scanf("%s",sa);
scanf("%s",sb);
ans[i]=fun(sa,sb);
}
for(i=0;i<line;i++)printf("%d\n",ans[i]);
return0;
}
解题思路:
利用动态规划的方法。建立一个arry[len_a][len_b]的二维数组,行数和列数皆从0开始,行数n,列数m分别代表字符串a的前n个字符,和字符串b的前m个字符,arry
[m]代表字符串a的前n个字符和字符串b的前m个字符之间的编辑距离。首先初始化二维数组的第一行和第一列,分别为方格所在列数和行数,让后按如下方法初始化每一个方格。
arry[i][j]=min{arry[i-1][j]+1,arry[i][j-1]+1,arry[i-1][j-1]+sa[i]!=sb[j]}
整体用公式表达:
编辑距离的应用:
DNA分析
拼字检查
语音辨识
抄袭侦测
相似度计算
解题方法的改进:DNA分析 http://poj.org/problem?id=3356 题目描述:
脱氧核糖核酸即常说的DNA,是一类带有遗传信息的生物大分子。它由4种主要的脱氧核苷酸(dAMP、dGMP、dCMT和dTMP)通过磷酸二酯键连接而成。这4种核苷酸可以分别记为:A、G、C、T。
DNA携带的遗传信息可以用形如:AGGTCGACTCCA.... 的串来表示。DNA在转录复制的过程中可能会发生随机的偏差,这才最终造就了生物的多样性。
为了简化问题,我们假设,DNA在复制的时候可能出现的偏差是(理论上,对每个碱基被复制时,都可能出现偏差):
1. 漏掉某个脱氧核苷酸。例如把 AGGT 复制成为:AGT
2. 错码,例如把 AGGT 复制成了:AGCT
3. 重码,例如把 AGGT 复制成了:AAGGT
如果某DNA串a,最少要经过 n 次出错,才能变为DNA串b,则称这两个DNA串的距离为 n。
例如:AGGTCATATTCC 与 CGGTCATATTC 的距离为 2
你的任务是:编写程序,找到两个DNA串的距离。
【输入、输出格式要求】
用户先输入整数n(n<100),表示接下来有2n行数据。
接下来输入的2n行每2行表示一组要比对的DNA。(每行数据长度<10000)
程序则输出n行,表示这n组DNA的距离。
例如:用户输入:
3
AGCTAAGGCCTT
AGCTAAGGCCT
AGCTAAGGCCTT
AGGCTAAGGCCTT
AGCTAAGGCCTT
AGCTTAAGGCTT
则程序应输出:
1
1
2
【注意】
请仔细调试!您的程序只有能运行出正确结果的时候才有机会得分!
在评卷时使用的输入数据与试卷中给出的实例数据可能是不同的。
请把所有函数写在同一个文件中,调试好后,拷贝到【考生文件夹】下对应题号的“解答.txt”中即可。
相关的工程文件不要拷入。
源代码中不能使用诸如绘图、Win32API、中断调用、硬件操作或与操作系统相关的API。
允许使用STL类库,但不能使用MFC或ATL等非ANSI c++标准的类库。
例如,不能使用CString类型(属于MFC类库),不能使用randomize, random函数(不属于ANSI C++标准)
结题代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int fun(charsa[],char sb[])
{
int len_a=strlen(sa),len_b=strlen(sb);
char arry[10000][10000];
int i,j;
int a,b,c,t;
for(i=0;i<=len_a;i++)
{
for(j=0;j<=len_b;j++)
{
if(i==0)arry[i][j]=j;
else if(j==0)arry[i][j]=i;
else
{
a=arry[i-1][j]+1;
b=arry[i][j-1]+1;
if(sa[i-1]==sb[j-1])c=arry[i-1][j-1];
elsec=arry[i-1][j-1]+1;
t=b<c?b:c;
arry[i][j]=a<t?a:t;
};
}
}
return arry[i-1][j-1];
}
int main()
{
int line,i;
int ans[100];
char sa[10000],sb[10000],e;
scanf("%d",&line);
e=getchar();
for(i=0;i<line;i++)
{
scanf("%s",sa);
scanf("%s",sb);
ans[i]=fun(sa,sb);
}
for(i=0;i<line;i++)printf("%d\n",ans[i]);
return 0;
}
解题总结:
1.我好不容易把这个程序编好了,然后又好不容易才发现int arry【10000】【10000】数组不能定义,估计占用空间太大,如果定义chararry[1000][1000]程序运行成功。
2.在定义变量的时候(尤其是指针,数组变量)首先给它赋一个初始值,以防在接下来的程序中没有赋值但是却引用了。
3.改进:可以让arry[10000][10000]动态的用arry[2][10000]生成,因为问题的本质是得到arry[10000][10000]元素就行了,并且根据每个元素生成的原理只需要两行就行了。
最长公共子序列:
问题描述:
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。
问题的递归式写成:
回溯输出最长公共子序列过程:
算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i
= 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m
+ n)。
代码:
#include <stdio.h>
#include <string.h>
#define MAXLEN 100
void LCSLength(char *x,char
*y,int m,int n,int
c[][MAXLEN],int b[][MAXLEN])
...{
int i, j;
for(i = 0; i<= m; i++)
c[i][0] = 0;
for(j = 1; j<= n; j++)
c[0][j] = 0;
for(i = 1;i<= m; i++)
...{
for(j = 1; j<= n; j++)
...{
if(x[i-1] == y[j-1])
...{
c[i][j] = c[i-1][j-1] + 1;
b[i][j] = 0;
}
elseif(c[i-1][j] >= c[i][j-1])
...{
c[i][j] = c[i-1][j];
b[i][j] = 1;
}
else
...{
c[i][j] = c[i][j-1];
b[i][j] = -1;
}
}
}
}
void PrintLCS(int b[][MAXLEN],char
*x,int i,int j)
...{
if(i == 0 ||j == 0)
return;
if(b[i][j]== 0)
...{
PrintLCS(b, x, i-1, j-1);
printf("%c ", x[i-1]);
}
elseif(b[i][j]== 1)
PrintLCS(b, x, i-1, j);
else
PrintLCS(b, x, i, j-1);
}
int main(int argc,char
**argv)
...{
char x[MAXLEN]=...{"ABCBDAB"};
char y[MAXLEN]=...{"BDCABA"};
intb[MAXLEN][MAXLEN];
intc[MAXLEN][MAXLEN];
int m, n;
m =strlen(x);
n =strlen(y);
LCSLength(x, y, m, n, c, b);
PrintLCS(b, x, m, n);
return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int fun(char *sa,char *sb)
{
inti,j,a,b,c,t;
int len_a=strlen(sa)+1,len_b=strlen(sb)+1;
int * arry=(int*)malloc(len_a*len_b*sizeof(int));//配合上文的说明如何用malloc返回值
for(i=0;i<len_a;i++) //强制转化为二重指针
{
for(j=0;j<len_b;j++)
{
if(i==0||j==0)arry[i*len_b+j]=0;//这是有一种方法
else
{
a=arry[(i-1)*len_b+j];
b=arry[i*len_b+(j-1)];
if(sa[i]==sb[j])c=1;
else c=0;
c=c+arry[(i-1)*len_b+(j-1)];
t=a>b?a:b;
arry[i*len_b+j]=t>c?t:c;
}
}
}
returnarry[(i-1)*len_b+(j-1)];
}
int main()
{
charsa[100];
charsb[100];
gets(sa);
gets(sb);
printf("%d",fun(sa,sb));
return0;
}
代码评价:
这个程序只能输出最长公共子序列的长度,而不能输出序列。思考如何才能输出有多个解的最长公共子序列。
动态规划理解:
我用五个字来总结动态规划,“最优子结构”,有别于通常说的最有子结构。
“子”:体现了动态规划最核心的步骤是找对象的子对象,任何事物都是由很多个“子”构成本身这个总体的。如对象是一个字符串是,它的“子”可以子串,对象是两个字符串时,它的“子”可以是任意两个字串的任意组合。具体还是视题意而定。
“最优”:在建立“子”与“子”之间的递推关系同时,选择最优解。
“结构”:不仅指“子”解是有一定的结构的,而且还指动态规划这一方法就是在一定的结构框架内完成的,还要多加参透。
附录:
题目标题:翻硬币
小明正在玩一个“翻硬币”的游戏。
桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:**oo***oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:
程序输入:
两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000
程序输出:
一个整数,表示最小操作步数
例如:
用户输入:
**********
o****o****
程序应该输出:
5
再例如:
用户输入:
*o**o***o***
*o***o**o***
程序应该输出:
1
题目分析:
咋看之下,这道题也是求俩个字符串之间的距离,但这道题有它的特殊之处在于操作不一样。所以我就从找规律的角度去做了,其实编辑距离这道题也能用找规律的方法去做,但是他考虑的情况有非常多种。而这道题不一样了,通过找规律发现规律很简单。这道题的算法可以不归入五大算法里面。
#include<stdio.h>
#include<string.h>
int main()
{
charsa[1000],sb[1000];
intc[1000]={0};
gets(sa);
gets(sb);
intsum=0,i=0,len=strlen(sa),a=0,b=0;
for(i=0;i<len;i++)if(sa[i]!=sb[i])c[i]=1;
for(i=0;i<len;i++)
{
if(c[i]==1)
{
a=i;
for(i=i+1;i<len;i++)if(c[i]==1)
{
b=i;
break;
}
sum+=(b-a);
}
}
printf("%d\n",sum);
return0;
}
其实编辑距离和最长公共子序列是对同一个问题的描述,都能够显示出两个字符串之间的“相似度”,即它们的雷同程度。而子序列与字串的区别在于字串是连续的,子序列可以不连续,只要下标以此递增就行。
编辑距离:
Problem description:
设A 和B 是2 个字符串。要用最少的字符操作将字符串A 转换为字符串B。这里所说的字符操作包括 (1)删除一个字符; (2)插入一个字符; (3)将一个字符改为另一个字符。将字符串A变换为字符串B 所用的最少字符操作数称为字符串A到B 的编辑距离,记为 d(A,B)。试设计一个有效算法,对任给的2 个字符串A和B,计算出它们的编辑距离d(A,B)。
Input
输入的第一行是字符串A,文件的第二行是字符串B。
Output
程序运行结束时,将编辑距离d(A,B)输出。
Sample Input
fxpimu
xwrs
Sample Output
5
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
int _Min(int a,int b,int c)
{
int min=a;
if (b <min)
min=b;
if(c <min)
min=c;
return min;
}
int ComputeDistance(char s[],char t[])
{
int n = strlen(s);
int m = strlen(t);
//int d[][] = new int[n + 1, m + 1]; // matrix
int **d = (int **)malloc((n+1) * sizeof(int *)); //如何用malloc返回值强制转化为二重指针
for(int i=0; i<=n; ++i)
{
d[i] = (int *)malloc((m+1) * sizeof(int));
}
// Step 1
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
// Step 2
for (int i = 0; i <= n; i++)
{
d[i][0] =i;
}
for (int j = 0; j <= m; d[0][j] = j++)
{
d[0][j] =j;
}
// Step 3
for (int i = 1; i <= n; i++)
{
//Step 4
for (int j = 1; j <= m; j++)
{
// Step 5
int cost = (t[j-1] == s[i-1]) ? 0 : 1;
// Step 6
d[i][j] = _Min(d[i-1][j]+1, d[i][j-1]+1,d[i-1][j-1]+cost);
}
}
// Step 7
return d
[m];
}
int main(int argc, char *argv[])
{
char a[9999];
char b[9999];
printf("请输入字符串1\n");
scanf("%s",&a);
printf("请输入字符串2\n");
scanf("%s",&b);
int result= ComputeDistance(a,b);
printf("%d\n",result);
system("PAUSE");
return 0;
}
////////////////////
Refrence : Dynamic Programming Algorithm(DPA) for Edit-Distance
编辑距离
关于两个字符串s1,s2的差别,可以通过计算他们的最小编辑距离来决定。
所谓的编辑距离: 让s1和s2变成相同字符串需要下面操作的最小次数。
1. 把某个字符ch1变成ch2
2. 删除某个字符
3. 插入某个字符
例如 s1 =
“12433”和s2=”1233”;
则可以通过在s2中间插入4得到12433与s1一致。
即 d(s1,s2) = 1 (进行了一次插入操作)
编辑距离的性质
计算两个字符串s1+ch1, s2+ch2的编辑距离有这样的性质:
1. d(s1,””) = d(“”,s1) =|s1| d(“ch1”,”ch2”)
=ch1 == ch2 ? 0 : 1;
2. d(s1+ch1,s2+ch2) = min( d(s1,s2)+ ch1==ch2 ? 0 : 1 ,
d(s1+ch1,s2),
d(s1,s2+ch2) );
第一个性质是显然的。
第二个性质: 由于我们定义的三个操作来作为编辑距离的一种衡量方法。
于是对ch1,ch2可能的操作只有
1. 把ch1变成ch2
2. s1+ch1后删除ch1 d =(1+d(s1,s2+ch2))
3. s1+ch1后插入ch2 d =(1 + d(s1+ch1,s2))
对于2和3的操作可以等价于:
_2. s2+ch2后添加ch1 d=(1+d(s1,s2+ch2))
_3. s2+ch2后删除ch2 d=(1+d(s1+ch1,s2))
因此可以得到计算编辑距离的性质2。
复杂度分析
从上面性质2可以看出计算过程呈现这样的一种结构(假设各个层用当前计算的串长度标记,并假设两个串长度都为
n )
可以看到,该问题的复杂度为指数级别 3的 n次方,对于较长的串,时间上是无法让人忍受的。
分析: 在上面的结构中,我们发现多次出现了(n-1,n-1),
(n-1,n-2)……。换句话说该结构具有重叠子问题。再加上前面性质2所具有的最优子结构。符合动态规划算法基本要素。因此可以使用动态规划算法把复杂度降低到多项式级别。
动态规划求解
首先为了避免重复计算子问题,添加两个辅助数组。
一. 保存子问题结果。
M[ |s1| ,|s2| ] , 其中M[ i , j ]表示子串 s1(0->i)与
s2(0->j)的编辑距离
二. 保存字符之间的编辑距离.
E[ |s1|, |s2| ] , 其中 E[ i, j ] = s[i] = s[j] ?0 : 1
三. 新的计算表达式
根据性质1得到
M[ 0,0] = 0;
M[ s1i, 0 ] = |s1i|;
M[ 0, s2j ] = |s2j|;
根据性质2得到
M[ i, j ] = min( m[i-1,j-1] + E[ i, j ] ,
m[i, j-1] ,
m[i-1,j] );
复杂度
从新的计算式看出,计算过程为
i=1 -> |s1|
j=1 -> |s2|
M[i][j] =
……
因此复杂度为 O( |s1| * |s2| ),如果假设他们的长度都为n,则复杂度为
O(n^2)
解题代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int fun(char sa[],char sb[])
{
intlen_a=strlen(sa),len_b=strlen(sb);
chararry[100][100]={0};
inti,j;
inta,b,c,t;
for(i=0;i<=len_a;i++)
{
for(j=0;j<=len_b;j++)
{
if(i==0)arry[i][j]=j;
elseif(j==0)arry[i][j]=i;
else
{
a=arry[i-1][j]+1;
b=arry[i][j-1]+1;
if(sa[i-1]==sb[j-1])c=arry[i-1][j-1];
else c=arry[i-1][j-1]+1;
t=b<c?b:c;
arry[i][j]=a<t?a:t;
};
}
}
return arry[i-1][j-1];
}
int main()
{
intline,i;
intans[100];
char sa[10000],sb[10000],e;
scanf("%d",&line);
e=getchar();
for(i=0;i<line;i++)
{
scanf("%s",sa);
scanf("%s",sb);
ans[i]=fun(sa,sb);
}
for(i=0;i<line;i++)printf("%d\n",ans[i]);
return0;
}
解题思路:
利用动态规划的方法。建立一个arry[len_a][len_b]的二维数组,行数和列数皆从0开始,行数n,列数m分别代表字符串a的前n个字符,和字符串b的前m个字符,arry
[m]代表字符串a的前n个字符和字符串b的前m个字符之间的编辑距离。首先初始化二维数组的第一行和第一列,分别为方格所在列数和行数,让后按如下方法初始化每一个方格。
arry[i][j]=min{arry[i-1][j]+1,arry[i][j-1]+1,arry[i-1][j-1]+sa[i]!=sb[j]}
整体用公式表达:
编辑距离的应用:
DNA分析
拼字检查
语音辨识
抄袭侦测
相似度计算
解题方法的改进:DNA分析 http://poj.org/problem?id=3356 题目描述:
脱氧核糖核酸即常说的DNA,是一类带有遗传信息的生物大分子。它由4种主要的脱氧核苷酸(dAMP、dGMP、dCMT和dTMP)通过磷酸二酯键连接而成。这4种核苷酸可以分别记为:A、G、C、T。
DNA携带的遗传信息可以用形如:AGGTCGACTCCA.... 的串来表示。DNA在转录复制的过程中可能会发生随机的偏差,这才最终造就了生物的多样性。
为了简化问题,我们假设,DNA在复制的时候可能出现的偏差是(理论上,对每个碱基被复制时,都可能出现偏差):
1. 漏掉某个脱氧核苷酸。例如把 AGGT 复制成为:AGT
2. 错码,例如把 AGGT 复制成了:AGCT
3. 重码,例如把 AGGT 复制成了:AAGGT
如果某DNA串a,最少要经过 n 次出错,才能变为DNA串b,则称这两个DNA串的距离为 n。
例如:AGGTCATATTCC 与 CGGTCATATTC 的距离为 2
你的任务是:编写程序,找到两个DNA串的距离。
【输入、输出格式要求】
用户先输入整数n(n<100),表示接下来有2n行数据。
接下来输入的2n行每2行表示一组要比对的DNA。(每行数据长度<10000)
程序则输出n行,表示这n组DNA的距离。
例如:用户输入:
3
AGCTAAGGCCTT
AGCTAAGGCCT
AGCTAAGGCCTT
AGGCTAAGGCCTT
AGCTAAGGCCTT
AGCTTAAGGCTT
则程序应输出:
1
1
2
【注意】
请仔细调试!您的程序只有能运行出正确结果的时候才有机会得分!
在评卷时使用的输入数据与试卷中给出的实例数据可能是不同的。
请把所有函数写在同一个文件中,调试好后,拷贝到【考生文件夹】下对应题号的“解答.txt”中即可。
相关的工程文件不要拷入。
源代码中不能使用诸如绘图、Win32API、中断调用、硬件操作或与操作系统相关的API。
允许使用STL类库,但不能使用MFC或ATL等非ANSI c++标准的类库。
例如,不能使用CString类型(属于MFC类库),不能使用randomize, random函数(不属于ANSI C++标准)
结题代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int fun(charsa[],char sb[])
{
int len_a=strlen(sa),len_b=strlen(sb);
char arry[10000][10000];
int i,j;
int a,b,c,t;
for(i=0;i<=len_a;i++)
{
for(j=0;j<=len_b;j++)
{
if(i==0)arry[i][j]=j;
else if(j==0)arry[i][j]=i;
else
{
a=arry[i-1][j]+1;
b=arry[i][j-1]+1;
if(sa[i-1]==sb[j-1])c=arry[i-1][j-1];
elsec=arry[i-1][j-1]+1;
t=b<c?b:c;
arry[i][j]=a<t?a:t;
};
}
}
return arry[i-1][j-1];
}
int main()
{
int line,i;
int ans[100];
char sa[10000],sb[10000],e;
scanf("%d",&line);
e=getchar();
for(i=0;i<line;i++)
{
scanf("%s",sa);
scanf("%s",sb);
ans[i]=fun(sa,sb);
}
for(i=0;i<line;i++)printf("%d\n",ans[i]);
return 0;
}
解题总结:
1.我好不容易把这个程序编好了,然后又好不容易才发现int arry【10000】【10000】数组不能定义,估计占用空间太大,如果定义chararry[1000][1000]程序运行成功。
2.在定义变量的时候(尤其是指针,数组变量)首先给它赋一个初始值,以防在接下来的程序中没有赋值但是却引用了。
3.改进:可以让arry[10000][10000]动态的用arry[2][10000]生成,因为问题的本质是得到arry[10000][10000]元素就行了,并且根据每个元素生成的原理只需要两行就行了。
最长公共子序列:
问题描述:
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。
问题的递归式写成:
回溯输出最长公共子序列过程:
算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i
= 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m
+ n)。
代码:
#include <stdio.h>
#include <string.h>
#define MAXLEN 100
void LCSLength(char *x,char
*y,int m,int n,int
c[][MAXLEN],int b[][MAXLEN])
...{
int i, j;
for(i = 0; i<= m; i++)
c[i][0] = 0;
for(j = 1; j<= n; j++)
c[0][j] = 0;
for(i = 1;i<= m; i++)
...{
for(j = 1; j<= n; j++)
...{
if(x[i-1] == y[j-1])
...{
c[i][j] = c[i-1][j-1] + 1;
b[i][j] = 0;
}
elseif(c[i-1][j] >= c[i][j-1])
...{
c[i][j] = c[i-1][j];
b[i][j] = 1;
}
else
...{
c[i][j] = c[i][j-1];
b[i][j] = -1;
}
}
}
}
void PrintLCS(int b[][MAXLEN],char
*x,int i,int j)
...{
if(i == 0 ||j == 0)
return;
if(b[i][j]== 0)
...{
PrintLCS(b, x, i-1, j-1);
printf("%c ", x[i-1]);
}
elseif(b[i][j]== 1)
PrintLCS(b, x, i-1, j);
else
PrintLCS(b, x, i, j-1);
}
int main(int argc,char
**argv)
...{
char x[MAXLEN]=...{"ABCBDAB"};
char y[MAXLEN]=...{"BDCABA"};
intb[MAXLEN][MAXLEN];
intc[MAXLEN][MAXLEN];
int m, n;
m =strlen(x);
n =strlen(y);
LCSLength(x, y, m, n, c, b);
PrintLCS(b, x, m, n);
return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int fun(char *sa,char *sb)
{
inti,j,a,b,c,t;
int len_a=strlen(sa)+1,len_b=strlen(sb)+1;
int * arry=(int*)malloc(len_a*len_b*sizeof(int));//配合上文的说明如何用malloc返回值
for(i=0;i<len_a;i++) //强制转化为二重指针
{
for(j=0;j<len_b;j++)
{
if(i==0||j==0)arry[i*len_b+j]=0;//这是有一种方法
else
{
a=arry[(i-1)*len_b+j];
b=arry[i*len_b+(j-1)];
if(sa[i]==sb[j])c=1;
else c=0;
c=c+arry[(i-1)*len_b+(j-1)];
t=a>b?a:b;
arry[i*len_b+j]=t>c?t:c;
}
}
}
returnarry[(i-1)*len_b+(j-1)];
}
int main()
{
charsa[100];
charsb[100];
gets(sa);
gets(sb);
printf("%d",fun(sa,sb));
return0;
}
代码评价:
这个程序只能输出最长公共子序列的长度,而不能输出序列。思考如何才能输出有多个解的最长公共子序列。
动态规划理解:
我用五个字来总结动态规划,“最优子结构”,有别于通常说的最有子结构。
“子”:体现了动态规划最核心的步骤是找对象的子对象,任何事物都是由很多个“子”构成本身这个总体的。如对象是一个字符串是,它的“子”可以子串,对象是两个字符串时,它的“子”可以是任意两个字串的任意组合。具体还是视题意而定。
“最优”:在建立“子”与“子”之间的递推关系同时,选择最优解。
“结构”:不仅指“子”解是有一定的结构的,而且还指动态规划这一方法就是在一定的结构框架内完成的,还要多加参透。
附录:
题目标题:翻硬币
小明正在玩一个“翻硬币”的游戏。
桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:**oo***oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:
程序输入:
两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000
程序输出:
一个整数,表示最小操作步数
例如:
用户输入:
**********
o****o****
程序应该输出:
5
再例如:
用户输入:
*o**o***o***
*o***o**o***
程序应该输出:
1
题目分析:
咋看之下,这道题也是求俩个字符串之间的距离,但这道题有它的特殊之处在于操作不一样。所以我就从找规律的角度去做了,其实编辑距离这道题也能用找规律的方法去做,但是他考虑的情况有非常多种。而这道题不一样了,通过找规律发现规律很简单。这道题的算法可以不归入五大算法里面。
#include<stdio.h>
#include<string.h>
int main()
{
charsa[1000],sb[1000];
intc[1000]={0};
gets(sa);
gets(sb);
intsum=0,i=0,len=strlen(sa),a=0,b=0;
for(i=0;i<len;i++)if(sa[i]!=sb[i])c[i]=1;
for(i=0;i<len;i++)
{
if(c[i]==1)
{
a=i;
for(i=i+1;i<len;i++)if(c[i]==1)
{
b=i;
break;
}
sum+=(b-a);
}
}
printf("%d\n",sum);
return0;
}
相关文章推荐
- Android开发周报:弹幕源码开放送
- Kmemleak检测工具介绍
- linux里的软件安装
- 2015.12 第一篇IT博
- C-041.局部变量跟全局变量的区别
- Object-C : Block的实现方式
- 2.4G/5G频段WLAN的模式、带宽、协商速率、加密方式
- 跨域的小小总结:js跨域及跨域的几种解决方法
- IOS版Unity的Google Analytics(分析)插件集成Admob管理库教程
- 通达OA服务器意外断电后数据库容易损坏的解决方案
- 黑马程序员--字节流
- Oracle中的部分查询
- 开始记录学习经历
- ios测试基础五: ios手机流量消耗
- 如何安装本地jar包到maven库
- 五、图标更换
- 自定义圆角imageview方案2
- Builder design pattern in java
- 【Java基础】Java中的持久属性集Properties
- WinForm与WPF下跨线程调用控件