离散序列的相似性度量

一 前言

离散序列通常是字符串序列,一个字符表示一种标签或者等级。这与定量型数据的相似性度量不同,定量型数据可以采用距离函数来度量相似度,而离散序列一般不具有数值计算的特性。故而,我们对离散序列的相似度量通常采用字符串比较的方法,本文讨论的是编辑距离最长公共子序列

二 编辑距离

编辑距离是将一个字符串转化为另一个字符串所使用一系列插入、删除和替换操作所需要的最小代价。 例如,将 ababab 转化成 bababa 最少需要两次:第一次删除第一个a,第二次在尾端插入一个a。假设删除和插入的成本都是1,那么 ababab 和 bababa 的编辑距离就是2。显然,编辑距离越小说明两个字符串越相似。

给定两个任意的字符序列X和Y,如何计算两者之间的编辑距离?显然,这是个动态规划的问题,需要找出它的递归模型。

E d i t ( i , j ) Edit(i,j) Edit(i,j)代表片段 X i X_i Xi Y j Y_j Yj的最小匹配代价。那么有4种可能:

  • X [ i ] ! = Y [ j ] X[i]!=Y[j] X[i]!=Y[j]时, X i X_i Xi插入一个字符:插入一个字符到 X i X_i Xi的末尾,使得它和 Y j Y_j Yj的最后一个字符相等。此时匹配了一个 Y j Y_j Yj的字符, j j j指针向前移动, i i i不动。 E d i t ( i , j ) = E d i t ( i , j − 1 ) + 插 入 成 本 Edit(i,j)=Edit(i,j-1)+插入成本 Edit(i,j)=Edit(i,j1)+
  • X [ i ] ! = Y [ j ] X[i]!=Y[j] X[i]!=Y[j]时, X i X_i Xi删除一个字符:删除 X i X_i Xi末尾的字符。此时 X [ i − 1 ] X[i-1] X[i1] Y [ j ] Y[j] Y[j] 可能相等也可能不等,j不动,i指针向前移动。 E d i t ( i , j ) = E d i t ( i − 1 , j ) + 删 除 成 本 Edit(i,j)=Edit(i-1,j)+删除成本 Edit(i,j)=Edit(i1,j)+
  • X [ i ] ! = Y [ j ] X[i]!=Y[j] X[i]!=Y[j]时, X i X_i Xi替换一个字符:用 Y j Y_j Yj的末尾字符替换掉 X i X_i Xi的末尾字符。此时 X i X_i Xi Y j Y_j Yj的末尾匹配, i i i j j j均向前移动。 E d i t ( i , j ) = E d i t ( i − 1 , j − 1 ) + 替 换 成 本 Edit(i,j)=Edit(i-1,j-1)+替换成本 Edit(i,j)=Edit(i1,j1)+
  • X [ i ] = = Y [ j ] X[i]==Y[j] X[i]==Y[j]时,什么都不做, i i i j j j均向前移动。 E d i t ( i , j ) = E d i t ( i − 1 , j − 1 ) Edit(i,j)=Edit(i-1,j-1) Edit(i,j)=Edit(i1,j1)

综上所述,求解编辑距离的递归模型如下
设 I i j 为 指 标 变 量 , X [ i ] = = Y [ j ] 时 I i j = 0 , 否 则 I i j = 1 \begin{aligned} 设I_{ij}为指标变量,X[i]==Y[j]时I_{ij}=0,否则I_{ij}=1 \end{aligned} IijX[i]==Y[j]Iij=0Iij=1

E d i t ( i , j ) = m i n { E d i t ( i , j − 1 ) + 插 入 成 本 E d i t ( i − 1 , j ) + 删 除 成 本 E d i t ( i − 1 , j − 1 ) + I i j ∗ 替 换 成 本 \begin{aligned} Edit(i,j)=min\begin{cases}Edit(i,j-1)+插入成本 & \\Edit(i-1,j)+删除成本&\\ Edit(i-1,j-1)+I_{ij}*替换成本\end{cases} \end{aligned} Edit(i,j)=minEdit(i,j1)+Edit(i1,j)+Edit(i1,j1)+Iij

递归模型的回溯情况:

  • i = = − 1 i==-1 i==1(X遍历完了),此时Y可能遍历完也可能没有( j > = − 1 j>=-1 j>=1),但是剩下的Y的字符都是要插入到X的末尾,故 E d i t ( i , j ) = ( j + 1 ) ∗ 插 入 代 价 Edit(i,j)=(j+1)*插入代价 Edit(i,j)=(j+1)
  • j = = − 1 j==-1 j==1(Y遍历完了),此时X可能遍历完也可能没有( i > = − 1 i>=-1 i>=1),但是剩下的X的字符都要删除,故 E d i t ( i , j ) = ( i + 1 ) ∗ 删 除 代 价 Edit(i,j)=(i+1)*删除代价 Edit(i,j)=(i+1)

重叠计算问题: 显然递归树中存在大量的重复计算的结点,引入memo备忘录来记录计算过的值,如果计算过就直接返回结果,不再递归计算下去。

代码实现:(假设插入、删除和替换的代价都是1)

#编辑距离
def editDistance(x,y):
    #备忘录,用于消除重复计算
    memo=dict()
    #动态规划函数,i,j是x,y对应的下标
    def  edit(i,j):
        #已经计算过了,不再重复计算
        if (i,j) in memo:
            return  memo[(i,j)]

        I = 1#指标因子
        #回溯条件
        #如果x遍历完了,y剩下的插入
        if i==-1:  return j+1
        #如果y遍历完了,x剩下的删除
        if j==-1:  return i+1
        #如果字符相等,什么都不做,指标I置0
        if x[i]==y[j]:I=0

        #状态转移
        memo[(i,j)] =min(
            edit(i,j-1)+1,#插入
            edit(i-1,j)+1,#删除
            edit(i-1,j-1)+I*1,#替换或者什么都不做
        )
        return  memo[(i,j)]
    return  edit(len(x)-1,len(y)-1)

三 最长公共子序列

子序列是指按照原始序列的顺序依次截取字符所组成的字符串。 子序列和子串是不同的概念,因为子串一定是连续的,子序列不一定是连续的。例如,agbfcgdhei 和 afbgchdiei两个字符串,ei 是共同的字串(一定是连续的),而abcde和fghi是共同的子序列(不一定连续)。我们可以认为,最长公共子序列LCSS是一个相似度函数,因为它可以度量出两个字符串最多有多少个排列相同的字符。

给定两个任意的字符串X和Y,如何求LCSS(X,Y)?显然,这也是个动态规划的问题,我们找到其递归模型。

扫描二维码关注公众号,回复: 13154624 查看本文章

d p ( i , j ) dp(i,j) dp(i,j)表示子串 X [ 0 : i ] X[0:i] X[0:i]和子串 Y [ 0 : j ] Y[0:j] Y[0:j]最长的子序列长度。那么有三种可能:

  • X [ i ] = = Y [ j ] X[i]==Y[j] X[i]==Y[j],末尾字符是相同的:那么 d p ( i , j ) dp(i,j) dp(i,j)为子串 X [ 0 : i − 1 ] X[0:i-1] X[0:i1]和子串 Y [ 0 : j − 1 ] Y[0:j-1] Y[0:j1]最长的子序列长度+1,即 d p ( i , j ) = d p ( i − 1 , j − 1 ) + 1 dp(i,j)=dp(i-1,j-1)+1 dp(i,j)=dp(i1,j1)+1
  • X [ i ] ! = Y [ j ] X[i]!=Y[j] X[i]!=Y[j],尝试缩小 X i X_i Xi序列继续匹配: d p ( i , j ) = d p ( i − 1 , j ) dp(i,j)=dp(i-1,j) dp(i,j)=dp(i1,j)
  • X [ i ] ! = Y [ j ] X[i]!=Y[j] X[i]!=Y[j],尝试缩小 Y j Y_j Yj序列继续匹配: d p ( i , j ) = d p ( i , j − 1 ) dp(i,j)=dp(i,j-1) dp(i,j)=dp(i,j1)

综上所述,递归模型为:

d p ( i , j ) = m a x { d p ( i − 1 , j − 1 ) + 1 X[i]==Y[j] d p ( i − 1 , j ) X[i]!=Y[j] d p ( i , j − 1 ) X[i]!=Y[j] \begin{aligned} dp(i,j)=max\begin{cases}dp(i-1,j-1)+1&\text{X[i]==Y[j]} \\dp(i-1,j)&\text{X[i]!=Y[j]}\\ dp(i,j-1)&\text{X[i]!=Y[j]}\end{cases} \end{aligned} dp(i,j)=maxdp(i1,j1)+1dp(i1,j)dp(i,j1)X[i]==Y[j]X[i]!=Y[j]X[i]!=Y[j]

递归回溯的情况:

  • i = = − 1 ∣ ∣ j = = − 1 i == -1||j==-1 i==1j==1,此时 X i X_i Xi Y j Y_j Yj是空串,那么肯定不会有公共子序列, d p ( i , j ) = 0 dp(i,j)=0 dp(i,j)=0

重叠计算问题: d p ( i − 1 , j − 1 ) dp(i-1,j-1) dp(i1,j1)可以由先计算 d p ( i − 1 , j ) dp(i-1,j) dp(i1,j)再计算 d p ( i , j − 1 ) dp(i,j-1) dp(i,j1)得,也可以颠倒顺序得。这样就会有两条路径计算出同一个结果,那么递归树中会出现重复结点。消除重叠计算同样是引入memo记录计算过的值。

代码实现:

#最长公共子序列
def longestCommonSubsequence(x, y):
    #消除重复计算
    memo = dict()
    def dp(i, j):
        #计算过的不再计算
        if (i, j) in memo:
            return memo[(i, j)]
        #回溯条件
        if i == -1 or j == -1:
            return 0
        #状态转移
        if x[i] == y[j]:
            memo[(i, j)] = dp(i - 1, j - 1) + 1
        else:
            memo[(i, j)] = max(dp(i - 1, j), dp(i, j - 1))
        return memo[(i, j)]
    
    return dp(len(x) - 1, len(y) - 1)

猜你喜欢

转载自blog.csdn.net/ZHT2016iot/article/details/117256987
今日推荐