字符串匹配算法

来源:互联网 发布:免费票据打印软件 编辑:程序博客网 时间:2024/05/17 02:27
朴素字符串匹配算法:
有一个目标串,如abcabcabcdedd,一个模板串,如abcabcd
判断模板串是否包含于目标串,是则求出匹配位置
朴素字符串匹配算法就是从目标串开头进行与模板串比较,发生不匹配时,则从目标串的下一个位置开始再与模板串比较,如上面例子匹配到d的时候失败,则目标串从下一位b开始再匹配,明显b与模板串的第0个字符a不匹配,所以,目标串再移一位,从c开始再与模板串比较,所以,该算法时间复杂度为O(n*m)。下面几个算法是对它的改进。



模式匹配算法:
举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”?
第一个字符串我们称为目标串,第二个的称为模板串。
一种很直接的方法是从目标串开始进行与模板串比较,发现不相等时,从目标串的下一位开始再与模板串比较,不过这样效率很低,时间复杂度为O(n*m),n表示目标串长度,m表示模板串长度。
比如:

0...2
3
4
5
6
7
8
9

目标串
...
A
B
C
D
A
B
A
...
模板串

A
B
C
D
A
B
D

当匹配到目标串的第9位时发生不匹配,其实可以直接从目标串的第7位开始(也就是第3位的后4位)再匹配,这样效率就大大提高了,后移多少位只与模板串有关,与目标串无关。后移的位数与模板串发生不匹配的位置有关,如第0位A不匹配时后移1位,第1位B不匹配时后移1位,第2位C不匹配时后移2位,第3位D不匹配时后移3位,第4位A不匹配时后移4位,第5位B不匹配时后移4位。。。
那怎么知道后移多少位呢,首先我们用一个数组next[]保存模板串的一些性质,上面模板串的next[]数组为:
A
B
C
D
A
B
D
0
0
0
0
1
2
0
怎么算出数组next[]?
next[0]对应的A与前面没有匹配所以next[0] = 0
next[1]对应的B与前面没匹配所以next[1] = 0
next[2] = 0,next[3] = 0
next[4]对应的A与前面对应,所以为1
next[5]:根据next[4]=1,所以next[5]对应的B要与第1个字符也就是next[1]对应的B比较,相等则next[5] = next[4]+1,否则再与第0个字符A比较,相等则next[5] = 1,否则next[5] = 0
next[6]:根据next[5]=2,所以next[6]对应的D要与第2个字符也就是next[2]对应的C比较,相等则next[6] = next[5]+1,否则再与第0个字符A比较,相等则next[6] = 1,否则next[6] = 0
如:ABCDABABCDABABDABCC
对应的next[]数组为:0,0,0,0,1,2,1,2,3,4,5,6,7,8,0,1,2,3,0
有了next[]那下面就来求某个字符不匹配时后移的位数:
如第k个字符不匹配,则:
移动位数 = 已匹配的字符数(也就是k,因为从0开始) – next[k-1]

模式匹配算法的平均时间复杂度为O(n+m)
最坏情况下模式匹配算法的时间复杂度为O(n*m),如,在aaaaaaaaaaab中找aaaab,只能每次移一位
模式匹配算法的空间复杂度位O(m)

C实现:

#include<stdio.h>

#include<string.h>

//计算next数组

void computeNext(int next[], char T[])

{

    int len = strlen(T);

    if (len == 0)

        return;

    next[0] = 0;

    for (int i = 1; i < len; i++)

    {

        if (T[i] == T[next[i - 1]])

            next[i] = next[i - 1] + 1;

        else

        {

            if (T[i] == T[0])

                next[i] = 1;

            else

                next[i] = 0;

        }

    }

    next[len] = -1;

}

//匹配字符串

int matchStr(char P[], char T[],int next[])

{

    int lenP = strlen(P);

    int lenT = strlen(T);

    int start = 0;

    while (start <= (lenP - lenT))

    {

        for (int i = 0; i < lenT; i++)

        {

            if (P[start + i] == T[i] && i == lenT - 1)

                return start;

            if (P[start + i] != T[i])

            {

                if (i == 0)

                    start++;

                else

                    start = start + (i - next[i - 1]);

                printf("temp start:%d\n", start);

                fflush(stdout);

                break;

            }

        }

    }

    return -1;

}

void main()

{

    char P[100] = "ABCDAAABCDABCDACDABCDABABCDABCDACDABCDABABCDABCDACD";

    char T[50] = "ABCDABABCDABCDACD";

    int next[50];

    computeNext(next, T);

    int k = 0;

    while (next[k] != -1)

    {

        printf("next[%d]=%d\n", k, next[k]);

        fflush(stdout);

        k++;

    }

    int start = matchStr(P, T, next);

    printf("start from:%d\n", start);

    fflush(stdout);

}

由于模式匹配算法最坏时间复杂度比较差,下面引入一种改进的算法:KMP算法,KMP算法不管什么情况时间复杂度都为O(n+m)


KMP算法(难点):
KMP算法每当不匹配时,利用已得到的部分匹配结果将模式向右移尽可能远的一段距离,这样一来就不存在回溯的情况了,所以KMP算法不管什么情况时间复杂度都为O(n+m),而上面的模式匹配算法中存在回溯的情况。
如:
目标串:a b a b c a b c a c b a b
模板串:a b c a c
下面红色代表失效
第一趟:
a b b c a b c a c b a b     此时i=2
a b c                                    此时j=2
第二趟:
a b a b c a b c a c b a b     从下标为2开始,这时记录不匹配的点i=6
      a b c a c                       模板串从0开始(这由next数组决定的),失配点j=4
第三趟:
a b b c a b c a c b a b    这里从下标为6的地方开始
              (a)b c a c             模板串从1开始(这由next数组决定的),(a)不用再比较了,

KMP算法中的next[j]表示第j个字符失配时,下一趟匹配时,模板串应该从下标为next[j]的地方开始,前面的不用再比较,比如next等于0表示模板串从第0位开始,目标串从失配处开始;next等于3表示模板串从第3位开始,目标串从失配处开始;特别的,next[0]一定为-1,如果这一点失配,模板串从0开始,且目标串从失配的下一位开始,实际上也就是模板串从-1位开始,如果next[0]=0会出问题,因为如果模板串从0开始且目标串从失配处开始,则一直比较的都是同一个位置,永远都前进不了。

下面求next数组(难点):
(1)next[0] = -1
(2)当j>0时:
next[j] = max{k|0<k<j且'T0...T(k-1)'='T(j-k)...T(j-1)'},也就是j之前的字符串是否有对称,这里的对称指:如abcxxxabcY,前面的abc与后面的abc相等,则next[9] = 3
如果不存在这么一个k,则next[j] = 0
如,模板串:a,b,a,a,b,c,a,c对应的next数组为-1,0,0,1,1,2,0,1

已知next[j]=k(也就是'T0...T(k-1)'='T(j-k)...T(j-1)'),怎么求next[j+1]?以下步骤可求:
(1)若Tj=Tk,则表明:'T0...T(k)'='T(j-k)...T(j)',所以:
next[j+1] = next[j]+1
(2)若Tj!=Tk,则按下面步骤计算:
设next[k]=k'(已知的)k一定大于k',想想为什么
如果Tj=Tk'(也就是Tj=T(next[k])),则next[j+1]=next[k]+1
如果Tj!=Tk',即Tj!=T(next[k]),则再比较Tj与T(next[k']),如果相等则next[j+1]=next[k']+1,不相等则继续比较,直到最后如果没有相等的,则next[j+1]=0

c实现:






















0 0
原创粉丝点击