【五十一】【算法分析与设计】KMP 算法,KMP 算法的作用,KMP 算法流程,KMP 算法证明,KMP 算法代码

 

目录

KMP 算法的作用,解决的问题

KMP 算法的流程

Next 数组

KMP 算法正式过程

KMP 算法的证明过程

Next 数组的求法

Next 数组求法的证明过程

KMP 算法代码

结尾


 

KMP 算法的作用,解决的问题

1.

首先给你一个字符串 str,然后又给你一个字符串 match,请你再 str 的子串中找,是否存在字符串与 match 匹配,如果找到了返回该子串在 str 的开始位置下标,如果没有找到返回 -1。

2.

暴力解法,遍历所有子串需要 O(N*M)的时间复杂度,KMP 算法可以只用 O(N)时间复杂度解决这个问题。

KMP 算法的流程与证明过程

Next 数组

1.

对于 match 数组的每一个元素都有一个对应的数值,存储在 next 数组中。

2.

match[i]对应 next[i]。

3.

next[i]存储的值是,i 位置前面部分的最长公共前后缀的长度。

i 位置前面部分是指 0~i-1 区间。

例如 0~i-1 区间对应字符串为 abcabc。

前缀序列指的是 a,ab,abc,abca,abcab。

后缀序列指的是 c,bc,abc,cabc,bcabc。

公共前后缀长度是指,前后缀序列相同的长度,例如 a 与 c 前后缀序列不相同,ab 与 bc 前后缀序列不相同,abc 与 abc 前后缀序列相同,长度为 3,abca 与 cabc 前后缀序列不相同,abcab 与 bcabc 前后缀序列不相同。

因此最长公共前后缀长度为 3。

4.

人为规定 next[0]=-1,next[1]=0。

KMP 算法正式过程

3799f6b243d04c0e9876766d1ee603f9.png

1.

如果 str[i~X-1]全部与 match[0~Y-1]匹配成功,此时 X 与 Y 匹配失败,Y=next[Y],Y 回退到对应 next 存储的下标位置。

然后新的 Y 下标与 X 继续匹配。

2.

如果 next[Y]==-1,此时说明 Y==0,X++,然后新的 X 下标和 Y 继续匹配。

3.

如果 X 与 Y 匹配成功,X++,Y++,新的 X 与新的 Y 继续匹配。

4.

直到 XY 其中一个越界,如果 X 越界说明 Y 还没有匹配完,后面没有字符了,说明匹配失败,返回 -1。

如果 Y 越界,说明 Y 匹配完了,匹配成功,对应的 str 匹配子串开始下标位置是 X-Y。

KMP 算法的证明过程

c5e41050f1474404bd77a48ae41691d3.png

1.

如果 str[i~X-1]全部与 match[0~Y-1]匹配成功,此时 X 与 Y 匹配失败,Y=next[Y],Y 回退到对应 next 存储的下标位置。

然后新的 Y 下标与 X 继续匹配。

1.

A 部分和 B 部分相同,C 部分与 B 部分相同,说明 A 部分和 C 部分相同,Y 回退到 next[Y]位置,相当于已经匹配完 AC 部分接下来匹配红色 Y 与 X 位置元素。

08b888b9317141a0a5f6a239b6f65807.png

2.

为什么 A 部分前面都可以不用考虑?

假设 A 部分前面开始匹配可以匹配成功,那么蓝色框框部分就是匹配成功的子串,上面蓝色部分 C 对应下面蓝色部分 C 是相同的,上面蓝色部分前面一段 C 和绿色框框 D 是相同的,也就是下面绿色 D 和蓝色 C 是相同的,那么 Y 的 next[Y]得到了一个更大的长度,我们 next[Y]最长公共前后缀是 BC 部分,现在推出一个更长的公共前后缀,推出矛盾,说明 A 部分前面都不用考虑。

2f772e972f08470fbfdd74a06e36d249.png

 

Next 数组的求法

ec2650d153884d0e82e8a7808cad9dc1.png

1.

假设 0~Y-1 的 next 都求完了,cn 是 Y-1 对应的 next 值。

如果 match[cn]==match[Y-1],next[Y]=cn+1。

2.

如果 match[cn]!=match[Y-1],cn=next[cn].

然后新的 cn 继续与 Y-1 进行判断。

3.

如果 next[cn]==-1,说明 cn==0,next[Y]=0。

4.

下一次计算的时候,cn 等于 Y-1 对应的 next 值,初始化。

Next 数组求法的证明过程

adf7fd5398c34538935be06ef42428fd.png

1.

首先 next[Y]最大是 A 的长度,当且仅当 match[next[Y-1]]==match[Y]的时候,取得。这是 next[Y]的极限。

不可能大于这个值,如果大于说明最长公共前后缀求错了,推出时矛盾的。

2.

如果 match[cn]!=match[y-1], cn=next[cn].

此时我们在判断紫色位置有没有可能是最长公共前后缀,也就是我们跳过了蓝色区间。也就是我们需要证明蓝色位置不可能是最长公共前后缀开始的位置。

同理,如果蓝色位置有可能成为答案,那么会推出最长公共前后缀求错了,推出矛盾,所以蓝色部分是不可能的区间位置。

3.以此类推....

KMP 算法代码

 
#include<bits/stdc++.h>
using namespace std;
class code01_KMP {
public:
        static int getIndexof(string str, string match) {
                if (str.empty() || match.empty() || str.size() < match.size())
                        return -1;
                int x = 0, y = 0;
                vector<int> next = getNextVector(match);
                while (x < str.size() && y < match.size()) {
                        if (str[x] == str[y]) {//如果相等往后移
                                x++;
                                y++;
                        } else if (next[y] != -1) {//如果不相等,y后退到next[y]位置
                                y = next[y];
                        } else {//如果next[y]==-1,即y==0,x++
                                x++;
                        }
                }//x-a+1=y+1--->a=x-y
                return y == match.size() ? x - y : -1;
        }
        static vector<int> getNextVector(string match) {
                if (match.size() == 1) return { -1 };
                vector<int> next(match.size());
                next[0] = -1;
                next[1] = 0;

                int i = 2;//从下标为2位置开始计算next
                int cn = 0;//i-1位置的next数值
                while (i < next.size()) {
                        if (match[i - 1] == match[cn]) {//如果i-1位置字符与cn相等,cn是某个公共前后缀长度,cn+1表示next[i]
                                next[i++] = ++cn;
                        } else if (next[cn] != -1) {//如果next[cn]!=-1,cn=nect[cn]
                                cn = next[cn];
                        } else {//如果nect[cn]==-1,cn不能等于nect[cn],next[i]==cn==0
                                next[i++] = 0;
                        }

                }

                return next;
        }
        static string getRandomString(int possibilities, int size) {//0-25
                srand(time(NULL));
                //string ans("", rand() % (size + 1));
                string ans("", size);
                for (int i = 0; i < ans.size(); i++) {
                        ans[i] = rand() % (possibilities + 1) + 'a';
                }
                return ans;
        }

};

int main() {
        int possibilities = 5;
        int strSize = 20;
        int matchSize = 5;

        cout << "test begin" << endl;
        string str = code01_KMP::getRandomString(possibilities, strSize);
        string match = code01_KMP::getRandomString(possibilities, matchSize);
        cout << "str:" << str << endl;
        cout << "match:" << match << endl;
        int pos = code01_KMP::getIndexof(str, match);
        if (pos != -1)
                cout << "匹配成功! 开始下标:" << code01_KMP::getIndexof(str, match) << endl;
        else cout << "匹配失败!" << endl;

}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

 

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

 

谢谢您的支持,期待与您在下一篇文章中再次相遇!

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值