KMP算法

前言:字符串匹配问题,学了很久的kmp ,一直没有学会,老实反复忘记,emmm,于是近几天又重新学习,似乎此次比之前要好很多。

 参考资料 kmpicon-default.png?t=N7T8https://www.bilibili.com/video/BV18k4y1m7Ar/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=b38f09e64081b3af352f47fde02581e3

 大家可以先看看这个视频,会好理解一点。

大家可能不理解的是nxt 数组 怎么求,我们通过视频可以了解到,假设 我们的模式串是这个“abcdabca”

按照视频的算法 他的nxt数组是从下标0开始的, 大部分博主的kmp算法都是从下标为1开始的,从acwing查询模板 是找这个样子

个人认为,记得住哪个,就记哪个。一定要理解,当然 ,你能背下来另外说了。

回归主题:视频中的kmp 求nxt数组,是自己跟自己比较,求出最大的前缀和后缀匹配的长度。

当然最大不可以是自己。比如说 aaaaa 那么就是 0 1 2 3 4 最后一个是4 而不是 5。 我们这里以下标为0 开始。

以这个图为例、 从视频得知,我们这里令 假设字符串长度为n  设置两个双指针 ,i = 0 ,j = 1,kmp 的本质 是 一个指针永远不会回退,这样我们一个指针 扫过的长度 永远是字符串长度。时间复杂度就是O(n) 。

全局设置nxt[N] 数组 默认值为0

那么 i = 0 j = 1  如果 s[i] == s[j] 那么对应的nxt[j]  = i + 1; 

为什么要等于i + 1 因为前面如果匹配了,加上这个字母匹配,是不是要加1.所以nxt[j] =i +  1;

i 表示前面有几个 字母相匹配。

大家到这里肯定很蒙蔽 orz  一切的前提请结合视频 再来观看。

然后 i++ j++ 

如果不匹配 那么 i 就回退到 上一个匹配到的下标 i = nxt[i-1]  我们是 索引是0 开始 所以 无需像 下标为1 开始一样 加1 。 直接用就是了。

回退什么时候停止呢? 一直回退到两个字符相等 或者 i = 0 为止。

下面我们模拟一遍。

i = 0 j = 1 发现两者不相等且i 不等于0 的时候 ,i 回退到 nxt[i-1]。 否则无需回退。如若回退, 我们得知 i= nxt[0] = 0 那么 回退停止 ,比较两者是否相等,相等则是 nxt[j] = i + 1  否则 nxt[j] = i 。

j 从1 到 3 都是这样 直到 j = 4  我们发现  s[i] == s[j] 那么j++ i++  nxt[4] = i + 1; 

同理nxt[5] = 2 nxt[6] = 3  当J = 7时候 发现 s[3] != s[7]  所以 i 回退 nxt[i-1] = nxt[2] = 0 

比较s[0] 和s[7] 相等  则是 i + 1 。从始至终 j 没有回退过,所以时间复杂度是O(n)。 

至此nxt数组 大致求法就结束了 下面贴出自己写的代码

void nx() {
    for (int i = 0, j = 1; j < s.size(); j++) {
        if (s[i] == s[j]) {
            nxt[j] = i + 1;
            i++;
        } else {
            while (s[i] != s[j] && i) {
                i = nxt[i - 1];
            }
            if (s[i] == s[j]) {
                nxt[j] = i + 1;
                i++;
            } else {
                nxt[j] = i;
                i = 0;
            }
            // 17 -22 简洁 变成
			// if(s[i] = s[j]  j++ 
			// nxt[j] = i 
        }
    }
}

kmp 算法与此也类似。现在模式串不是和自己比较了 而是跟文本串比较。

大致代码贴如下

void kmp(string s ,string t){
	int ss = s.size();
	int tt = t.size();
	for(int i = 0 ,j = 0;i<ss;i++){
		// 默认ss 是text  tt 是pattern
	while(s[i] != p[j] && j ){
		 j  = nxt[j-1];
    }
    
    if(s[i] == p[j]){
		j++;
    }
    // 主要是注意顺序 
	if(j == tt){
		    cout <<i-tt + 1<<" ";
	    	j =nxt[j-1];
		}
	}
	
}

完整代码如下

#include <bits/stdc++.h>

using namespace std;

int nxt[100];
string s,p;

void nx() {
    for (int i = 0, j = 1; j < s.size(); j++) {
        if (s[i] == s[j]) {
            nxt[j] = i + 1;
            i++;
        } else {
            while (s[i] != s[j] && i) {
                i = nxt[i - 1];
            }
            if (s[i] == s[j]) {
                nxt[j] = i + 1;
                i++;
            } else {
                nxt[j] = i;
                i = 0;
            }
            // 17 -22 简洁 变成
			// if(s[i] = s[j]  j++ 
			// nxt[j] = i 
        }
    }
}

void kmp(string s ,string t){
	int ss = s.size();
	int tt = t.size();
	for(int i = 0 ,j = 0;i<ss;i++){
		// 默认ss 是text  tt 是pattern
	while(s[i] != p[j] && j ){
		 j  = nxt[j-1];
    }
    
    if(s[i] == p[j]){
		j++;
    }
    // 主要是注意顺序 
	if(j == tt){
		    cout <<i-tt + 1<<" ";
	    	j =nxt[j-1];
		}
	}
	
}
void clearConsole() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}

int main() {
 
   
//    while (1) {
//        memset(nxt, 0, sizeof(nxt));
//        cin >> s;
//        cin.ignore(); // 忽略之前输入的回车
//        nx();
//        for (int i = 0; i < s.size(); i++) {
//            cout << nxt[i] << " ";
//        }
//        cout << endl;
//        cin.get(); // 等待用户按下回车
//        system("cls");
//    }
    cin>>s>>p;
    nx();
    kmp(s,p);
    return 0;
}

最后: 大家一定要看视频,要有自己的思考,写代码,并不是说抄就可以了,你抄这么多代码,并没有什么用,emm 反而会陷入一种过拟合的状态,你只会做你知道的,但是没见过的,但是算法其实都讲过,你还是不会写。就很尴尬了,然后回望自己写的文章,发现emm一言难尽,并不是书面化的表达。个人能力有限,仍需多加学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值