字符串匹配算法分析--BF和KMP算法

     串的模式匹配,即子串(模式串)在主串中的定位操作,是各种串运算处理中的最重要的操作之一。

在处理此类问题时,有两个比较常用的算法,分别是最简单的BF算法和改进后的KMP算法。

1.BF算法

——-算法原理

设有两个串:主串S和子串T,从主串S的第一个字符S0开始和子串T中的第一个字符t0比较,并分别用变量i和j指示串S和串T中正在比较的字符位置,如果相等,则继续比较两个串当点位置的猴急字符,否则从主串S的第二个字符串s1处开始再重新与串T中第一个字符t0进行比较。依此类推,指导子串中的每个字符按顺序和主创中的一个连续字符序列中的每个字符依次相等,则匹配成功并返回子串T中第一个字符t0在主串S中出现的位置。
否则匹配失败,返回-1。

——–代码实现

public class BF_StrIndex {
    public static int bfofStrIndex(String S, String T) {
        int i = 0, j = 0;//用变量i和j指示串S和串T中正在比较的字符位置
        while (i < S.length() && j < T.length()) {
            if (S.charAt(i) == T.charAt(j)) {
                i++;
                j++;
            } else {
                i = i - j + 1;//一轮匹配失败后,新一趟的匹配应该与串S中的第(i-j+1)位进行比较。
                j = 0;//一轮匹配失败后,从串从第一个位置从新开始比较。
            }
        }
        if (j >= T.length()) {
            return i - T.length();
        } else
            return -1;
    }
}

———算法复杂度
假定:m为子串的长度,而成功匹配的主创中,开始位置可以由0到n-m;
最好情况下:BF 算法的平均复杂度为O(n + m);
最坏情况下:BF 算法的平均复杂度为O(n * m);


2.KMP算法

———算法改进

基于BF算法进行较大改进,主要是消除了主串指针的回溯(回调),从而使算法效率有了较大的提高。
造成BF算法执行速度较慢的原因是有回溯的存在,而这些回溯并非是必要的。
改进措施:当出现si不等于tj时,无需将指针i回调到i-j+1的位置,而是决定下一步应该由T中的哪一个字符来和主串S中的字符si进行比较。对于模式串来说,我们会提前计算出每个匹配失败的位置应该移动的距离,并存放于next[]数组中,这花费的时间是常数时间。

——–求next[]数组

注意!!
next[0]为-1;
next[1]为0;


求子串T的next[]数组的值与主串S无关,而只与子串T本身有关。假设next[j]=k,则说明此时在子串T中有”t0t1…..tk-1“==”tj-ktj-k+1…..tj-1“,其中下标k满足0< k< j的某个最大值。此时计算next[j+1]有两种情况:

(1)若tk=tj,则表明在子串中有”t0t1…..tk“==”tj-ktj-k+1…..tj“并且是满足条件的最大K,所以有next[j+1]=next[j]+1=k+1;


(2)若tk!=tj则表明在子串中有”t0t1…..tk“!=”tj-ktj-k+1…..tj“则将子串T向右滑动至子串中的next[k]字符来和子串中第i个字符继续比较。即k=next[k].
相等则有next[j+1]=next[j]+1=k+1
不等则继续k=next[k],直到不存在满足条件的K时,则有next[j+1]=0.


代码实现

public static void GetNext(String T, int[] next) {
        int j = 0, k = -1;
        next[0] = -1;
        while (j < T.length() - 1) {
            if (k == -1 || T.charAt(j) == T.charAt(k)) {
                j++;
                k++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
    }

————-KMP算法的执行思想

假设S为主串而T为子串,并设变量i和j指示串S和串T中正在比较的字符位置,令i和j的初值均为0。若有si=tj,则i和j分别加1;否则i不变,j回退到j=next[j]的位置(即子串T向右滑动)。接下里在再次比较si和tj,相等则分别加1;否则i不变,j继续回退到j=next[j]的位置。依此类推,直到:
1.j回退到某个j=next[j]的位置,有si=tj,则i和j分别加1继续比较。
2.j回退到j=0,则从主串S的下一个字符si+1和子串第一个字符t0开始重新匹配;

显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用!

———-完整KMP算法实现


public class KMP_StrIndex {

//得到next[]数组。
    public static void GetNext(String T, int[] next) {
        int j = 0, k = -1;
        next[0] = -1;
        while (j < T.length() - 1) {
            if (k == -1 || T.charAt(j) == T.charAt(k)) {
                j++;
                k++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
    }
//KMP算法执行
    public static int KmpIndex(String S, String T) {
        int t_len = T.length(), i = 0, j = 0;
        int[] next = new int[t_len];
        GetNext(T, next);
        while (i < S.length() && j < T.length()) {
            if (j == -1 || S.charAt(i) == T.charAt(j)) {
                j++;
                i++;
            } else
                j = next[j];
        }
        if (j == T.length()) {
            return i - T.length();
        }
        return -1;
    }

}

———时间复杂度分析

设主串S的长度为n,子串T的长度为m.
在KMP算法中求next[]数组时间复杂度O(m),在随后的匹配中因主串S的下表i并不减少(即不产生回溯),所以比较次数可以记为n。
所以KMP算法总的时间复杂度为O(m+n).


2.2 getNext()算法的改进。

不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

显然,发生问题的原因在于P[j] == P[next[j]]。

所以我们也只需要添加一个判断条件即可:

public static int[] getNext(String ps) {

    char[] p = ps.toCharArray();

    int[] next = new int[p.length];

    next[0] = -1;

    int j = 0;

    int k = -1;

    while (j < p.length - 1) {

       if (k == -1 || p[j] == p[k]) {

           if (p[++j] == p[++k]) { // 当两个字符相等时要跳过

              next[j] = next[k];

           } else {

              next[j] = k;

           }

       } else {

           k = next[k];

       }

    }

    return next;

} 

ok,KMP 就告一段落了!

你可能感兴趣的:(java,学习笔记,技术分享,数据结构与算法之温故而知新,剑指offer)