一、背景
如果要检测我的这篇是不是抄袭的教材上的内容,只需要把我的这篇文章与课本上的每一篇文章进行比对,我的这篇文章与课本上的每一篇文章比对后会有个相强调内容似度,找出相似度最大的课本中的文章,记为TEXT,这个时候就可以怀疑我抄袭的TEXT。这件事交给计算机做,就要给它个idea——动态规划之序列的连配。
二、正题
用一个具体的例子进入正题。
假设我的Text1:I am Wang Lao.
教材上Text2的内容:I em Wan Laooo.
Text3的内容:I am Wan Lao.(这里对于Text中的空格忽略不计,也即是把它们看成连续的字符串,便于一下讨论)
这时要比较Text1 and Text2,Text3的相似度,交给计算机怎么办?首先计算机要知道一下两件事:
第一、Text1 and Text2,Text3的内容,记为:t1,t2,t3。
第二、Text1 and Text2,Text3的长度,记为:l1,l2,l3
先讨论t1与t2的相似度:用分值的高低表示
第一点,比较t1与t2的长度,我们发现l1+1=l2。长度不一,或者说多抄袭或者少抄袭一个字母了怎么办,用空格补齐,空格在text中添加在哪 比较合适,这要看在比对的过 程添加空格后不同的text得分高低,得分高的位置取胜。比分过程见下。
第二点,是计分:对应的字母进行比对(1)抄袭错误-不同减一分,如:a 与 e。抄袭对了-相同加一分,如:m 与 m。
(2)多抄袭了一个字母减两分,如wang 与wan 多个g,这时g与空格比对。
(3)少抄袭一个字母减两分,如 Lao与Laoo少个o,这是可看作o与空格比对。
注意:在实际的应用中一个字母变为另外一个字母的分是通过概率统计出来的。
t1与t2比较如下:(空格用下划线表示,空格的位置是枚举出的,下面只是介绍三种加空格方案)
第一种加空格方案:IamWangLao_ _
IemWan_Laooo
得分:aScore_t12=1-1+1+1+1+1-2+1+1+1-2-2=5(应该没加错)
第二种加空格方案:IamWang_Lao
IemWanLaooo
得分:bScore_t12=1-1+1+1+1+1-1-2-1-1+1=0;
在这里很明显第一种加空格的方案取胜
t1与t3的比较过程同上,最终的最高得分假设为cScore,实际操作结果aScore>cScore,那么我们就可以判定我的这篇文章是抄袭的t2
这里有前辈总结出的公式,大致如下:
score[i,j]=max(compare(a[i],b[i])+score[i-1,j-1],compare(a[i],”“)+score[i-1,j],compare(““,b[j])+score[i,j-1])
待比较字符串,a,长度m,字符串,b,长度n。0
alignment(){
for(i=0;i<=m;i++)
score[i,0]=-2*i;
for(j=0;j<=n;j++)
score[0,j]=-2*j;
for(i=0;i<=m;i++)
for(j=0;j<=m;j++){
score[i,j]=max(compare(a[i],b[i])+score[i-1,j-1],compare(a[i],"_")+score[i-1,j],compare("_",b[j])+score[i,j-1])
}//for
}
得到的score[i,j]可以看成一个表格,在这个表格里有一条从出发点没到终结点的路径,这条路径的得分最高,表示序列a极大的可能按照这条变成b。也就是说这条路径表示的相似度最高。
这种全局匹配的算法的空间复杂度很高,实际应用中使用的是一个叫Hirschberg的前辈,他有个很聪明的idea,把空间的复杂度降低。怎么降低呢,上述中score[i,j]这个表格中,每个位置元素的值跟左邻,上邻,斜对角上邻的三个元素相关,有兴趣的小伙伴可以画出这个二维的表格看看,这个前辈就想出用两个数组来记录每次循环时相邻的两列的得分记录。这样空间复杂度从O(m*n)降到O(m+n),这样在全局连配的过程中节省下的空间是很可观的,伪代码如下
linearSpace(a[],b)[],record[]){
for(i=0;i<m;i++)
sore[i]=-2*i;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
newscore[j]=max(compare(a[i],b[j])+score[j-1],score[j]-3,newscore[j-1]-3);
newscore[0]=0;
for(k=1;k<=n;k++)
score[k]=newscore[k];
return score;
}
这种算法不能记录从序列b到序列a的变化的路线,怎么办呢?我们使用分治的思想,假装已经有了最优的solution。先把序列a二分,让a的前一半与后一般分别一序列连陪,连配后我们就可以得到最高得分的position,这个position就记录了b的中间字符如何变为a的相应位置的字符,如打错字符了,少写了,多写了。用一个数组Array[]把这个position记录下来,递归这个过程就可以解决我们这个问题,这个idea,最关键的是如何找b的划分位置,记为pb。这个idea的空间复杂度仍然是O(m+n)。
算法的伪代码如下:
linearSpaceAlignment(a[],b[]){
definition:aScore[m],bScore[m]
linearSpace(a[1...m/2],b[n],aScore);
linearSpace(a[m/2+1...m],b[n],bScore);
let q0=argmaxq(aScore[q]+bScore[q]);
free array aScore[],bScore[];
record(m/2,q0) into Array[];
linearSpaceAlignment(a[1...m/2],b[1...q0]);
linearSpaceAlignment(a[m/2+1...m],b[q0+1...n])
return Array[];
}
总结:一个大的问题无法解决的话就划分成小问题去解决,划分的方法主要有两种(1)增量式(2)分治。动态规划就是要规避掉划分时冗余的事物。