字符串距离-相似度

来源:互联网 发布:python json 汉字 编辑:程序博客网 时间:2024/04/28 02:11

来自编程之美:

问题描述:

许多程序会大量使用字符串。对于不同的字符串,我们希望能够有办法判断其相似程序。我们定义一套操作方法来把两个不相同的 字符串变得相同,具体的操作方法为:

  1.修改一个字符(如把“a”替换为“b”);

  2.增加一个字符(如把“abdd”变为“aebdd”);

  3.删除一个字符(如把“travelling”变为“traveling”);

  比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加/减少一个“g”的方式来达到目的。上面的两种方案,都仅需要一 次 。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度 为1/2=0.5。

  给定任意两个字符串,你是否能写出一个算法来计算它们的相似度呢?


  分析与解法  

  不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。

  我们还是就住集中考虑如何才能把这个问题转化成规模较小的同样的子问题。如果有两个串A=xabcdae和B=xfdfa,它们的第一个字符是相同的,只要计算A[2,...,7]=abcdae和B[2,...,5]=fdfa的距离就可以了。但是如果两个串的第一个字符不相同,那么可以进行如下的操作(lenA和lenB分别是A串和B串的长度)。

 1.删除A串的第一个字符,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。

 2.删除B串的第一个字符,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。

 3.修改A串的第一个字符为B串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。

 4.修改B串的第一个字符为A串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。

 5.增加B串的第一个字符到A串的第一个字符之前,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。

 6.增加A串的第一个字符到B串的第一个字符之前,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。

  在这个题目中,我们并不在乎两个字符串变得相等之后的字符串是怎样的。所以,可以将上面的6个操作合并为:

  1.一步操作之后,再将A[2,...,lenA]和B[1,...,lenB]变成相字符串。

  2.一步操作之后,再将A[2,...,lenA]和B[2,...,lenB]变成相字符串。

  3.一步操作之后,再将A[1,...,lenA]和B[2,...,lenB]变成相字符串。

总结:

如果两个字符串A,B,若第一个字母相同,那么,其距离d就为distance(A[2-n],B[2-m])。

若首字母不相同,那么距离d就为min(distance(A[1-n],B[2-m]), distance(A[2-n],B[1-m]), distance(A[2-n],B[2-m]))+1。

这样,很快就可以完成一个递归程序。

#include<stdio.h>

#include<stdlib.h>

 

int min(int t1,int t2,int t3)

{

    int m = t1;

    if(m > t2)

        m = t2;

    if(m > t3)

        m = t3;

    return m;

}

 

int strDis(char *str1, int start1, int end1, char *str2, int start2, int end2)

{

    if(start1 > end1)

    {

        if(start2 > end2)

            return 0;

        else

            return end2-start2+1;

    }

    

    if(start2 > end2)

    {

        if(start1 > end1)

            return 0;

        else

            return end1-start1+1;

    }

    

    if(str1[start1] == str2[start2])

    {

        return strDis(str1, start1+1, end1, str2, start2+1, end2);

    }

    else 

    {

        int t1,t2,t3;

        t1 = strDis(str1, start1, end1, str2, start2+1, end2);

        t2 = strDis(str1, start1+1, end1, str2, start2, end2);

        t3 = strDis(str1, start1+1, end1, str2, start2+1, end2);

        return min(t1,t2,t3)+1;

    }

}

 

int main()

{

     char *str1 = "hello";

     char *str2 = "ehallo";

     int distance;

 

     distance = strDis(str1,0,4,str2,0,5);

     printf("%d\n",distance);

     system("pause");

     return 0;

}

上面的递归程序,有什么地方需要改进呢?在递归的过程中,有些数据被重复计算了。为了避免重复计算,我们可以将计算后的结果存储起来。如何修改递归程序呢?这个问题留给读者自己完成吧!

一个直观的办法就是搞个全局变量(2维数组,两个字符串的长度+1作为数组的两个维数),递归程序中每计算出一个结果,就把相应的记过保存到数组里面。因此,在调用strDis进行计算之前,应该先检查该值是否已经计算过,如果没有计算过,则调用strDis进行计算;如果已经计算过,则直接使用即可。

进一步想象递归程序调用过程,可看出一个现象:前面的计算结果依赖于后面的计算结果。比如,按上面的程序,strDis(str1,0,4,str2,0,5)依赖于strDis(str1,1,4,str2,0,5)、strDis(str1,0,4,str2,1,5)和strDis(str1,1,4,str2,1,5)。那么我们是不是可以反过来计算,先计算出“后面的”,然后根据“后面的”计算结果直接求出前面的值,而免去递归调用。这样就需要对二维数组进行重新定义:

int distance[str1_len][str2_len];

distance[i][j] 表示 str1的最后i个字符 和 str2的最后j个字符 之间的距离。

这样就可得到:

distance[0][0] = 0

distance[0][j] = j  (  0=<j<=str2_len )

distance[i][0] = i  (  0=<i<=str1_len )

当计算 distance[i][j] 时,distance[i-1][j-1]、distance[i][j-1]、distance[i-1][j] 都已经计算出来,因此可以很方便的计算出distance[i][j]。

按这种思路可能程序:

#include<stdio.h>

#include<stdlib.h>

 

int min(int t1,int t2,int t3)

{

    int min = t1;

    if(min > t2)

        min = t2;

    if(min > t3)

        min = t3;

    return min;

}

int strDis(char *str1, int str1_len, char *str2, int str2_len)

{

    int distance[str1_len][str2_len];

    int i,j;

    int t1,t2,t3;

    for(i=0;i<=str1_len;i++) 

    {

        distance[i][0] = i;

    }

    for(j=0;j<=str2_len;j++)

    {

        distance[0][j] = j;

    }

    

    for(i=1;i<=str1_len;i++)

        for(j=1;j<=str2_len;j++)

        {

            t1 = distance[i-1][j]+1;

            t2 = distance[i][j-1]+1;

            if(str1[str1_len-i]==str2[str2_len-j])

                t3 = distance[i-1][j-1];

            else

                t3 = distance[i-1][j-1]+1;

            distance[i][j] = min(t1,t2,t3);

        }

    return distance[str1_len][str2_len];

}

int main()

{

    int i,j;

    char *str1 = "hello";

    char *str2 = "ehallo";

    int dis = strDis(str1,5,str2,6);

    printf("%d\n",dis);

    system("pause");

    return 0;

}

注意这句:if(str1[str1_len-i]==str2[str2_len-j])

表示str1最后i个字符中的第一个  和 str2最后j个字符中的第一个 进行比较。

这样,问题就搞定了。