数据结构-模式匹配

261 阅读2分钟

简单模式匹配(暴力匹配)

int index(string_ch s,string_ch t){
    int i=1,j=1;
    while(i<=s.length&&j<=t.length){
        if(s.str[i]==t.str[j]){
            ++i;
            ++j;
        }else{
            i=i-j+2;
            j=1;
        }
    }
    if(j>t.length) return i-t.length;
    else return 0;
}

这是一种最典型的模式匹配算法,就是让变量i从母串当前位置出发,变量j从子串从起始位置出发。如果两个位置字符一致,就让i和j都+1;反之变量i回退到本循环的最初位置的下一位置出发,变量j从起始位置出发,继续进行匹配操作。到最后如果j的值大于子串的长度,就返回子串在母串中的起始位置,反之就返回0,代表母串中没有和参数对应的子串。

这种匹配方式的优点就是比较简单,基本上提到模式匹配就能够写出对应的程序。但缺点非常明显,这种算法时间复杂度太大,为O(mn)级,如果串长较长,就会浪费很多时间。而且该种匹配操作中存在大量的多余判断,比如如果在匹配中途发现匹配失败,简单模式匹配会将已经匹配的字串再次匹配一次。所以这种方法虽然直观,但并不优秀。

KMP算法

现在字符串模式匹配最常用的是KMP算法,他的时间复杂度只有O(m+n)级,不会再次匹配已经匹配过的字串。但KMP算法相对于暴力匹配来说,解释起来比较繁琐,这里我先由浅入深,从KMP算法的next数组记起。

next数组

在KMP算法中,相较于简单模式匹配,函数中除了有母串和子串作为参数,还需要有一个子串的next数组。next数组用于如果匹配失败,子串直接向右滑动到next[j]位。比如,如果在匹配到第4位时匹配失败,那么子串变量j直接滑动到next[4]位,如果next[4]=2,那么j就直接恢复到2,无需再从头开始。

计算机求next数组方法比较复杂,我们也可以人工求子串的next数组,一般来说,next数组的前两位都是{0,1},所以我们继续向下求即可。

写出两行串,一行足够长的未知串,一行子串。假设子串匹配到第i位时出现错误,就在第i位前画一条竖线,并将子串向右移,看看未知串的已知部分是否与竖线左边的子串一致,如果不一致继续向后移,如果一致,就记录下竖线右边子串数据元素的个数,那么这个个数就是next[i]的值。以此类推,最终得到next数组的全部值。

KMP算法的实现

int kmp(string_ch s,string_ch t,int next[]){
    int i=1,j=1;
    while(i<=s.length&&j<=t.length){
        if(s.str[i]==t.str[j]||j==0){
            ++i;
            ++j;
        }else{
            j=next[j];
        }
    }
    if(j>t.length) return i-t.length;
    else return 0;
}

有了next数组,KMP算法就很方便实现。