- 为什么要从后往前比较?
首先要明确的是,字符串匹配的速度很大程度取决于模式串往后移动的速度,模式串往后移动越快,算法就越高效。
![1ee43831272e0fa0ef0c662dc0e0453f.png](https://img-blog.csdnimg.cn/img_convert/1ee43831272e0fa0ef0c662dc0e0453f.png)
1和2无法匹配,所以我们要后移模式串。因为并不清楚一后面的4个字符是什么,如果移动的位数过多,很有可能导致错过正确的匹配,所以我们只能后移一位。
![8ef1f8dc30e25111ee22439f5aead8d4.png](https://img-blog.csdnimg.cn/img_convert/8ef1f8dc30e25111ee22439f5aead8d4.png)
如果从后往前匹配呢,如图1和2无法匹配。但是模式串中根本没有2,无论我们怎么移动模式串都无法匹配,所以模式上可以直接后移4位。
上面的例子有一个缺陷,如果从前往后匹配一旦匹配不上,我们就可以直接后移字符串。但是如果从后往前匹配,一旦匹配不上,我们需要检查该字符串在模式串中是否存在。如果不存在,我们才可以将模式串向后移动更多的位数。我们检查模式串中是否有该字符时,同样需要比较。这么看,似乎从后往前比较并没有什么明显的优势。
但是如果我们能够提前将不匹配的字符是否在模式串中出现或者出现在第几位的信息记录下来,那岂不是就可以跳过每一位的比较直接后移了。
这就涉及到接下来要讲的坏字符表。
- 坏字符是什么?
坏字符并不是某一个或某几个特定的字符,一旦我们遇到了无法匹配并且需要后移的字符,这个字符就称作是坏字符。
![5462818b29fef52eec6e70bf740d6c51.png](https://img-blog.csdnimg.cn/img_convert/5462818b29fef52eec6e70bf740d6c51.png)
如果我们想要匹配模式串"6789"。模式串的末尾9刚好和目标串中的一个数4对上。显然是不匹配的,那我们就要把模式串往后移动四位。
如果9不是和目标串中的4对应,而是和目标串中的7对应。那么后移四位可能就意味着我们会错过正确的匹配。
![8b57db634945464b43bb3f33f20aa14f.png](https://img-blog.csdnimg.cn/img_convert/8b57db634945464b43bb3f33f20aa14f.png)
很明显这时候应该后移两位,7和9不匹配,但是如果将模式串右移两位就可以和模式串中的7匹配。也就是说,移动的原则,是将模式串右移到刚好和坏字符匹配的位置。
按照这个规律,我们可以建立一个坏字符表。这个表记录着所有的ascii值,以及一旦模式串中的最后一个字符遇上这些ascii值的时候应该后移多少位。
举个例子,如果模式串是"6789"。坏字符表中1对应的就是后移四位,7对应的就是后移两位。9对应的就是后移零位,一旦后移的位数是0我们就不往后移动模式串,直接比较前一个字符是否匹配。
通常我们搜索文本的关键字不会太长,这也就意味着坏字符表中绝大部分的字符对应的位数都和模式串的长度相等。
建立好坏字符表之后,我们就不会直接比较模式串和目标字符串。先去坏字符表中找,只有当坏字符表中对应字符为零时才会开始比较模式串和目标字符串。
- 什么是好后缀?
坏字符的基本思想是节省重复比较的时间,遇到对应的字符,就直接往后移动模式串。我们在这个思想上更进一步,如果我们不仅仅是比较单个字符,而是比较一个子串,那就可以更快地移动子串。
![a6af17301f3721ebb778de64a8a370c8.png](https://img-blog.csdnimg.cn/img_convert/a6af17301f3721ebb778de64a8a370c8.png)
当我们比较完倒数第一个2和倒数第二个1之后,如果倒数第三个3不匹配,我们就要向后移动字符串。子串"12"在模式串中出现过两次,分别是开头和结尾。当我们移动模式串的时候,只要把开头的子串"12"和目标串中的"12"对齐就可以了。因为移动到中间的任何一个位置,目标串中的子串"12"都无法和模式串中的对应位置匹配上。
像这种从后往前数能够匹配上的子串就叫做好后缀。一旦我们有了匹配成功的后缀,就可以在匹配失败的时候,将模式串移动到后缀下一次出现的地方。但是我怎么知道该子串下一次出现的位置在哪呢?
这里我们采取的方法和之前一样,提前将后缀在模式串里重复出现的位置记录下来,一旦匹配失败,我们就可以根据这个记录直接移动模式串的位数。这个记录就叫做好后缀表。
如果模式串是"12312",比如后缀"12"对应的后移位数就是3。但是如果后缀是"312"呢,模式串中没有重复的后缀子串,那不管我们怎么移动也是无法匹配的,所以我们也可以直接把模式串移到末尾。所以"312"对应的后移位数就是5。
但是我们的好后缀表真的只是简单的把“后缀”和后移位数对应起来吗?当然不是,后缀的末尾都是模式串的最后一位,所以不同的后缀区别就在于起始位置不同,所以我们的好后缀表就可以从后缀和后移位置之间的关系改成后缀起始位置与后移位数之间的关系。比如后缀"12"匹配上了,3没有匹配上。那我就要把3在模式传中的位置“2”(下标从0开始)对应的值变成需要后移的位数“3”。
- 好后缀还是坏字符?
如果你理解了好后缀原则和坏字符原则,就会发现虽然他们的思路一脉相承,但是落实到具体问题上会有细微的差异。
![6e39b724c1508c7b6b2d96da6c95602e.png](https://img-blog.csdnimg.cn/img_convert/6e39b724c1508c7b6b2d96da6c95602e.png)
如图,好后缀"12"匹配上了。但是模式串中倒数第3位的3和目标串中的0无法匹配。这时如果我们按照坏字符原则就要将模式串后移移位,让模式串中的0和目标字符串中的0对应。但是如果我们按照好后缀原则,就要将模式串后移三位,让模式串中第1个"12"与目标子串中的"12"对应。但是如果是下面这种情况呢?
![2670fc86b384695c519cf859433657ae.png](https://img-blog.csdnimg.cn/img_convert/2670fc86b384695c519cf859433657ae.png)
倒数第三位的2和0无法匹配。如果按照好后缀原则,可以后移两位。但是按照坏字符原则则可以后移三位。
可以发现,在特定情况下。按照不同的规则移动模式串的效率是不一样的,那到底该选哪个呢?
答案很简单,择优录取。目的就是为了快速移动字符串,那当然是哪个移动得快选哪个啦。
算法实现之后有空再写吧,大家也可以去网上自己搜一下。