串和序列的编辑方法

关于字符串的的编辑距离的计算,最经典的两个方法就是树编辑距离和串编辑距离。网页的相似度就是根据这两个方法做到的。
(1)字符串编辑距离算法(转自http://hxraid.iteye.com/blog/615469,谢谢Heart.X.Raid啦~~~)
我们来看一个实际应用。现代搜索技术的发展很多以提供优质、高效的服务作为目标。比如说:baidu、google、sousou等知名全文搜索系统。当我们输入一个错误的query="Jave" 的时候,返回中有大量包含正确的拼写 "Java"的网页。当然这里面用到的技术绝对不会是我们今天讲的怎么简单。但我想说的是:字符串的相似度计算也是做到这一点的方法之一。


字符串编辑距离: 是一种字符串之间相似度计算的方法。给定两个字符串S、T,将S转换成T所需要的删除,插入,替换操作的数量就叫做S到T的编辑路径。而最短的编辑路径就叫做字符串S和T的编辑距离。

举个例子:S=“eeba” T="abac" 我们可以按照这样的步骤转变:(1) 将S中的第一个e变成a;(2) 删除S中的第二个e;(3)在S中最后添加一个c; 那么S到T的编辑路径就等于3。当然,这种变换并不是唯一的,但如果3是所有变换中最小值的话。那么我们就可以说S和T的编辑距离等于3了。


动态规划解决编辑距离
动态规划(dynamic programming)是一种解决复杂问题最优解的策略。它的基本思路就是:将一个复杂的最优解问题分解成一系列较为简单的最优解问题,再将较为简单的的最优解问题进一步分解,直到可以一眼看出最优解为止。

动态规划算法是解决复杂问题最优解的重要算法。其算法的难度并不在于算法本身的递归难以实现,而主要是编程者对问题本身的认识是否符合动态规划的思想。现在我们就来看看动态规划是如何解决编辑距离的。

还是这个例子:S=“eeba” T="abac" 。我们发现当S只有一个字符e、T只有一个字符a的时候,我们马上就能得到S和T的编辑距离edit(0,0)=1(将e替换成a)。那么如果S中有1个字符e、T中有两个字符ab的时候,我们是不是可以这样分解:edit(0,1)=edit(0,0)+1(将e替换成a后,在添加一个b)。如果S中有两个字符ee,T中有两个字符ab的时候,我们是不是可以分解成:edit(1,1)=min(edit(0,1)+1, edit(1,0)+1, edit(0,0)+f(1,1)). 这样我们可以得到这样一些动态规划公式:
如果i=0且j=0 edit(0, 0)=1
如果i=0且j>0 edit(0, j )=edit(0, j-1)+1
如果i>0且j=0 edit( i, 0 )=edit(i-1, 0)+1
如果i>0且j>0 edit(i, j)=min(edit(i-1, j)+1, edit(i,j-1)+1, edit(i-1,j-1)+f(i , j) )

小注:edit(i,j)表示S中[0.... i]的子串 si 到T中[0....j]的子串t1的编辑距离。f(i,j)表示S中第i个字符s(i)转换到T中第j个字符s(j)所需要的操作次数,如果s(i)==s(j),则不需要任何操作f(i, j)=0; 否则,需要替换操作,f(i, j)=1 。

这就是将长字符串间的编辑距离问题一步一步转换成短字符串间的编辑距离问题,直至只有1个字符的串间编辑距离为1。


编辑距离的实际应用
在信息检索领域的应用我们在文章开始的时候就提到了。另外,编辑距离在自然语言文本处理领域(NLP)中是计算字符串相似度的重要方法。一般而言,对于中文语句的相似度处理,我们很多时候都是将词作为一个基本操作单位,而不是字(字符)。


字符串编辑距离源代码
Java代码
package net.hr.algorithm.stroper;
/**
* 字符串编辑距离
*
* 这是一种字符串之间相似度计算的方法。
* 给定字符串S、T,将S转换T所需要的插入、删除、替代操作的数量叫做S到T的编辑路径。
* 其中最短的路径叫做编辑距离。
*
* 这里使用了一种动态规划的思想求编辑距离。
*
* @author heartraid
*
*/
public class StrEditDistance {

/**字符串X*/
private String strX="";
/**字符串Y*/
private String strY="";
/**字符串X的字符数组*/
private char[] charArrayX=null;
/**字符串Y的字符数组*/
private char[] charArrayY=null;

public StrEditDistance(String sa,String sb){
this.strX=sa;
this.strY=sb;
}
/**
* 得到编辑距离
* @return 编辑距离
*/
public int getDistance(){
charArrayX=strX.toCharArray();
charArrayY=strY.toCharArray();
return editDistance(charArrayX.length-1,charArrayY.length-1);
}
/**
* 动态规划解决编辑距离
*
* editDistance(i,j)表示字符串X中[0.... i]的子串 Xi 到字符串Y中[0....j]的子串Y1的编辑距离。
*
* @param i 字符串X第i个字符
* @param j 字符串Y第j个字符
* @return 字符串X(0...i)与字符串Y(0...j)的编辑距离
*/
private int editDistance(int i,int j){
if(i==0&&j==0){
//System.out.println("edit["+i+","+j+"]="+isModify(i,j));
return isModify(i,j);
}
else if(i==0||j==0){
if(j>0){
//System.out.println("edit["+i+","+j+"]=edit["+i+","+(j-1)+"]+1");
if(isModify(i,j) == 0) return j;
return editDistance(i, j-1) + 1;
}
else{
//System.out.println("edit["+i+","+j+"]=edit["+(i-1)+","+j+"]+1");
if(isModify(i,j) == 0) return i;
return editDistance(i-1,j)+1;
}
}
else {
//System.out.println("edit["+i+","+j+"]=min( edit["+(i-1)+","+j+"]+1,edit["+i+","+(j-1)+"]+1,edit["+(i-1)+","+(j-1)+"]+isModify("+i+","+j+")");
int ccc=minDistance(editDistance(i-1,j)+1,editDistance(i,j-1)+1,editDistance(i-1,j-1)+isModify(i,j));
return ccc;
}

}
/**
* 求最小值
* @param disa 编辑距离a
* @param disb 编辑距离b
* @param disc 编辑距离c
*/
private int minDistance(int disa,int disb,int disc){
int dismin=Integer.MAX_VALUE;
if(dismin>disa) dismin=disa;
if(dismin>disb) dismin=disb;
if(dismin>disc) dismin=disc;
return dismin;
}
/**
* 单字符间是否替换
*
* isModify(i,j)表示X中第i个字符x(i)转换到Y中第j个字符y(j)所需要的操作次数。
* 如果x(i)==y(j),则不需要任何操作isModify(i, j)=0; 否则,需要替换操作,isModify(i, j)=1。
* @param i 字符串X第i个字符
* @param j 字符串Y第j个字符
* @return 需要替换,返回1;否则,返回0
*/
private int isModify(int i,int j){
if(charArrayX[i]==charArrayY[j])
return 0;
else return 1;
}

/**
* 测试
* @param args
*/
public static void main(String[] args) {
System.out.println("编辑距离是:"+new StrEditDistance("eeba","abac").getDistance());
}

}
(2)PAT Tree子串匹配结构(转自http://hxraid.iteye.com/blog/615295,还得谢谢Heart.X.Raid啦~~~)
Patricia Tree 简称PAT tree。 它是 trie 结构的一种特殊形式。是目前信息检索领域应用十分成功的索引方
法,它是1992年由Connel根据《PATRICIA——Patrical Algorithm to Retrieve Information Coded in Alphanumeric》算法发展起来的。

PAT tree 在字符串子串匹配 上有这非常优异的表现,这使得它经常成为一种高效的全文检索算法,在自然语言处理领域也有广泛的应用。其算法中最突出的特点就是采用半无限长字串(semi-infinite string 简称 sistring) 作为字符串的查找结构。

采用半无限长字串(sistring): 一种特殊的子串信息存储方式。
比如一个字符串CUHK。它的子串有C、CU、CUH、CUHK、U、UH、UHK、H、HK、K十种。如果有n个字符的串,就会有n(n+1)/2种子串,其中最长的子串长度为n。因此我们不得不开辟 n(n+1)/2个长度为n的数组来存储它们,那么存储的空间复杂度将达到惊人的O(n^3)级别。

但是我们发现这样一个特点:
CUHK —— 完全可以表示 C、CU、CUH、CUHK
UHK —— 完全可以表示 U、UH、UHK
HK —— 完全可以表示 H、HK、
K —— 完全可以表示 K
这样我们就得到了4个sistring: CUHK、UHK、HK和K。

PAT tree的存储结构
如果直接用单个字符作为存储结点,势必构造出一棵多叉树(如果是中文字符的话,那就完蛋了)。检索起来将会相当不便。事实上,PAT tree是一棵压缩存储的二叉树结构。现在我们用“CUHK”来构造出这样一棵PAT tree 。

开始先介绍一下PAT tree的结点结构(看了后面的过程就再来理解这些概念)
* 内部结点:用椭圆形表示,用来存储不同的bit位在整个完整bit sequence中的位置。
* 外部节点(叶子结点): 用方形表示,用来记录sistring的首字符在完整sistring中的开始位置(字符索引)和sistring出现的频次。
* 左指针:如果 待存储的sistring在 内部结点所存储的bit位置上的数据 是0,则将这个sistring存储在该结点的左子树中。
* 右指针:若数据是1,则存储在右子树中。

(1) 将所有sistring的字符转化成1 bytes的ASCII码值,用二进制位来表示。形成一个bit sequence pattern(没有的空字符我们用0来填充)。

sistring bit sequence
完整sistring -> CUHK 010 00011 01010101 01001000 01001011 <- 完整bit sequence
UHK0 010 10101 01001000 01001011 00000000
HK00 01001000 01001011 00000000 00000000
K000 01001011 00000000 00000000 00000000




(2) 从第一个bit开始我们发现所有sistring的前3个bit位都相同010,那么相同的这些0/1串对于匹配来说就毫无意义了,因此我们接下来发现第4个bit开始有所不同了。UHK 的第4个bit是1,而CUHK、HK、K的第4个bit是0。则先构造一个内部结点iNode.bitSize=4(第4个bit),然后将UHK的字符索引 cIndex=2(UHK的开始字符U在完整的CUHK的第2位置上)构造成叶子结点插入到iNode的左孩子上,而CUHK、HK、K放在iNode右子树中。(如下图2)

(3) 递归执行第2步,将CUHK、HK、K进一步插入到PAT tree中。流程如下图所示。所有sistring都插入以后结束。
注意:既然PAT tree 是二叉查找树,那么一定要满足二叉查找树的特点。所以,内部结点中的bit 位就需要满足,左孩子的bit位< 结点bit 位< 右孩子的bit 位。


PAT tree的检索过程

利用PAT tree可以实现对语料的快速检索,检索过程就是根据查询字串在PAT tree中从根结点寻找路径的过程。当比较完查询字串所有位置后,搜索路径达到PAT tree的某一结点。

若该结点为叶子结点,则判断查询字串是否为叶子结点所指的半无限长字串的前缀,如果判断为真,则查询字串在语料中出现的频次即为叶子结点中记录的频次;否则,该查询字串在语料中不存在。

若该结点为内部结点,则判断查询字串是否为该结点所辖子树中任一叶子结点所指的半无限长字串的前缀。如果判断为真,该子树中所有叶子结点记录的频次之和即为查询字串的出现频次。否则,查询字串在语料中不存在。

这样,通过PAT tree可以检索原文中任意长度的字串及其出现频次,所以,PAT tree也是可变长统计语言模型优良的检索结构。


例如:要查找string= “CU ”(bit sequence=010 00 0 1 1 01010101) 是不是在CUHK 中。
(1) 根据“CUHK ”的PAT tree 结构( 如上图) ,根结点r 的bit position=4 ,那么查找bit sequence 的第4 个bit=0 。然后查找R 的左孩子rc 。
(2) rc 的bit position=5 ,在bit sequence 的第5 个bit=0 。则查找rc 的左孩子rcc 。
(3) rcc= ” CUHK ” 已经是叶子结点了,则确定一下CU 是不是CUHK 的前缀即可。


PAT tree 的效率

特点:PAT tree查找的时间复杂度和树的深度有关,由于树的构造取决于不同bit位上0,1的分布。因此PAT tree有点像二叉查找树 ,最坏情况下是单支树(如上图例子),此时的时间复杂度是O(n-1),n为字符串的长度。最好情况下是平衡二叉树 结构,时间复杂度是O(log2(N))。另外,作为压缩的二叉查找树,其存储的空间代价大大减少了。



PAT tree的实际应用

PAT tree在子串匹配上有很好的效率,这一点和Suffix Tree(后缀树),KMP算法的优点相同。因此PAT tree在信息检索和自然语言处理领域是非常常用的工具。比如:关键字提取,新词发现等NLP领域经常使用这种结构。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值