字符串匹配算法
BF算法是如何工作的?
正如同它的全称BruteForce一样,BF算法使用简单粗暴的方式,对主串和模式串进行逐个字符的比较:
第一轮,模式串和主串的第一个等长子串比较,发现第0位字符一致,第1位字符一致,第2位字符不一致:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422457_3801213fb80e7bec3253e957dc05fe3e9a506ba9.png)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422546_0b46f21fbe096b637cb8eb08f918c042eaf8acf3.png)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422530_342ac65c103853438a4805fb6038f778cb808813.png)
第二轮,模式串向后挪动一位,和主串的第二个等长子串比较,发现第0位字符不一致:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422520_a8014c086e061d95898960ad8fdf4dd763d9ca0e.png)
第三轮,模式串继续向后挪动一位,和主串的第三个等长子串比较,发现第0位字符不一致:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471424499_0e2442a7d933c895a41094aa243834f6830200a2.png)
以此类推,一直到第N轮:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422453_0824ab18972bd407347b478d83a2d9570fb30937.png)
当模式串挪动到某个合适位置,逐个字符比较,发现每一位字符都是匹配时,比较结束:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471422455_b151f8198618367aee40d660d858ccd2b21ce570.png)
BF算法的缺点很明显,效率实在太低了,每一轮只能老老实实地把模式串右移一位,实际上做了很多无谓的比较。
而BM算法解决了这一问题。它借助“坏字符规则”和“好后缀规则”,在每一轮比较时,让模式串尽可能多移动几位,减少无谓的比较。
利用BM算法,上面的主串和模式串匹配只需要比较三轮:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471557396_d1a20cf431adcbef68605c6a5b8469dba3cc9f33.png)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471557381_8644ebf81a4c510f4320a20d9772622bd52aa5f7.jpeg)
KMP算法的整体思路
KMP算法的整体思路是什么样子呢?让我们来看一组例子:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471853809_e4dde71190ef76c64c169238603dbafcae51678b.png)
KMP算法和BF算法的“开局”是一样的,同样是把主串和模式串的首位对齐,从左到右对逐个字符进行比较。
第一轮,模式串和主串的第一个等长子串比较,发现前5个字符都是匹配的,第6个字符不匹配,是一个“坏字符”:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471853781_962bd40735fae6cd2c36d00df898482243a70f89.png)
这时候,如何有效利用已匹配的前缀 “GTGTG” 呢?
我们可以发现,在前缀“GTGTG”当中,后三个字符“GTG”和前三位字符“GTG”是相同的:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471855794_0824ab18972bd407bb7ae85e85a2d9570eb30981.png)
在下一轮的比较时,只有把这两个相同的片段对齐,才有可能出现匹配。这两个字符串片段,分别叫做最长可匹配后缀子串和最长可匹配前缀子串。
第二轮,我们直接把模式串向后移动两位,让两个“GTG”对齐,继续从刚才主串的坏字符A开始进行比较:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471853829_5bafa40f4bfbfbed5defc2a08ddbb030aec31f69.png)
显然,主串的字符A仍然是坏字符,这时候的匹配前缀缩短成了GTG:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471853786_b812c8fcc3cec3fde3ecf3f2d2a493398694271c.png)
按照第一轮的思路,我们来重新确定最长可匹配后缀子串和最长可匹配前缀子串:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471853875_ae51f3deb48f8c547da29de8cc026af3e1fe7fb2.png)
第三轮,我们再次把模式串向后移动两位,让两个“G”对齐,继续从刚才主串的坏字符A开始进行比较:
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595471855752_10dfa9ec8a136327d9c6d8786ba4e7ea08fac700.png)
以上就是KMP算法的整体思路:在已匹配的前缀当中寻找到最长可匹配后缀子串和最长可匹配前缀子串,在下一轮直接把两者对齐,从而实现模式串的快速移动。
1. 对模式串预处理,生成next数组
2. 进入主循环,遍历主串
2.1. 比较主串和模式串的字符
2.2. 如果发现坏字符,查询next数组,得到匹配前缀所对应的最长可匹配前缀子串,移动模式串到对应位置
2.3.如果当前字符匹配,继续循环
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595472096539_a6efce1b9d16fdfa4c049e5c4ca4cb5295ee7bce.jpeg)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595472098520_f3d3572c11dfa9ec60b0614594fbb005908fc1fc.jpeg)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595472098267_7aec54e736d12f2e917580dab7e99264843568cd.jpeg)
![](https://uploadfiles.nowcoder.com/files/20200723/923791066_1595472096306_738b4710b912c8fc89c983750428d543d6882105.jpeg)