KMP算法与next数组的理解与求法

1、理解KMP算法:


首先注意以下一个事实:
       当模式串在某一个位置失配时,【该位置之前的所有字符】已经是和主串完全匹配的,不然根本不会能够到达现在失配的这个位置。

       如下图,当模式串与主串进行匹配,在比较‘a’与‘b’时,发现'a' != 'b'。但既然能到达比较‘a’这一步,就说明模式串的字符 ‘a’之前的字符一定是与主串完全匹配的。这时候如果我们已经提前知道【串1】与【串2】是相等的,那我们是不是可以直接把模式串往右推,推到模式串的【串1】与主串的【串2】重合的位置,这样模式串一下子飞快地来到这一步,这个推的距离就是我们说的next值。所以next数组其实就是考虑【模式串在每一个位置失配的情况下】能滑动的距离,next数组是完全与主串无关的。而滑动多远是看【模式串失配点之前字符串】的特点的,比如例子中【‘a’ 之前的字符串】的特点就是它的【最前面的一些字符组成的字符串“串1”】与【最后面的一些字符串组成的“串2”】是相等的,因而直接用“串1”代替“串2”再进行比较。

  

2、求next数组:

       我们手动地求next数组其实就是直接看模式串【失配点之前的字符串】的最前面与最后面相等的字符串到底可以有多长。但在程序上实现时求next数组其实也用到KMP算法的思想。

 


       首先,第一个位置的前面没有字符串,即【第一个位置之前的字符串】长度为0,所以第一个位置的next值不可能超过0(因为【某个位置之前的字符串--str】的最长前缀串的长度必须得是不能超过str的长度的),故令它为-1即可。同理,可以直接令next[1] = 0;

       

      下面是一般情况(比如我们要求下图绿色位置的next值):
       最理想的情况是(下图第一种情况):有str[i] == str[j],这时i,j都往右移动(此时i指向绿色位置),则有next[i] = j,因为其实匹配的【最长前缀串“串1”】与【最长后缀串“串2”】都只是简单的增加多了一个字符。

       但很多情况不会是第一种情况那么完美(下图第二种情况):这时有str[i] != str[j](即图中'b' != 'a') ,但是我们可以想办法转成第一种情况。这时我们退而求其次,利用“串11” == “串12” == “串21” == “串22”,可以知道,“串11”与“串22”是相等的。虽然“串12”后面那个字符是'a'而不是'b',但是说不定“串11”后面那个字符恰巧就是'b'呢,所以把j移动到“串11”的下一个位置,这样又可以下一次比较了,直到出现情况1时【绿色为置的next值】才真正被求出来。而这个j的移动方法,其实就是令j = next[j].


3、代码实现
 

//#include "pch.h"    //VS环境下需要这句
    #include <iostream>
    #include <string>
    using namespace std;
    
    
    //主串类,每次接收匹配串时都要先求匹配串的next数组 
    class MyString {
    private:
        //主串: 
        string mainStr;
    
        //获取模式串的next数组的函数: 
        int* getNext(string patStr) {
            //next数组:
            int* next;
            int i, j;
    
            //申请内存
            next = new int[patStr.length()];
    
            //初始化
            i = 0, j = -1, next[0] = -1;
    
            //开始求除了第一个位置之外的next值,i指向想要求next的位置
            while (i < patStr.length()-1) {
                //图中情况1:(包含着j退回到-1的情况)
                if (j == -1 || patStr[i] == patStr[j]) {
                    i++;
                    j++;
    
                    //一定注意,是在i++,j++之后才有以下这句:
                    next[i] = j;
                }
    
                //图中情况2:
                else {
                    j = next[j];
                }
            }
    
            //返回结果:
            return next;
        }
    
    public:
        MyString(string mainStrM) {
            mainStr = mainStrM;
        }
    
        int findByKMP(string patStr) {
            int* next;
            int i, j;
            int result;
    
            //成功获取所有next
            next = getNext(patStr);
            //输出next:
            cout << "模式串的next:  ";
            for (i = 0; i < patStr.length(); i++) {
                cout << next[i] << " ";
            }
            cout << endl;
    
    
            //以下是通过next进行查找的代码
            //i指向主串,j指向模式串
            i = 0, j = 0;
            while (true) {
                //模式串的长度已经不足以出现匹配的情况,直接结束
                if (i - j + patStr.length() > mainStr.length()) {
                    result =  -1;
                    break;
                }
    
                else {
                    //当前字符能够匹配,或者j已经退回-1了,i,j都要往右走
                    if (j == -1 || mainStr[i] == patStr[j]) {
                        i++, j++;
    
                        //检查匹配的字符是否是模式串的最后一个字符,如果是,匹配成功
                        if (j == patStr.length()) {
                            result = i - patStr.length() + 1;
                            break;
                        }
                    }
    
                    //不能匹配时,i不动,模式串往右滑动,即j更新为next[j]
                    else {
                        j = next[j];
                    }
                }
            }
    
            //回收内存:
            delete[] next;
    
            return result;
        }
    };
    
    int main() {
        //测试次数
        int times;
        //主串与模式串
        string mainStr, patStr;
        //查找结果
        int result;
    
        cout << "输入测试次数:";
        cin >> times;
        while (times--) {
            cout << "输入主串与模式串:";
            cin >> mainStr >> patStr;
    
            //构造主串对象
            MyString ms(mainStr);
    
            cout << "主串:" << mainStr << "    模式串:" << patStr << endl;
            //进行查找:
            result = ms.findByKMP(patStr);
            //输出结果:
            cout << "查找结果: "<< result << endl << endl << endl;
        }
    
        return 0;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值