做leetcode做到了一道实现strStr()
函数的题,实际上就是字符串匹配算法,令我不由得联想到了鼎鼎有名的KMP
算法.随后在网上又搜到了比KMP
还快,还简单的算法,what???啊饶了我吧orz
Brute Force算法
暴力算法…我就是用这种算法过leetcode的,毕竟没时间要求
简而言之,就是字符串与模式串中的字符一个个匹配,如果不匹配,则字符串字符进一位,模式串从头匹配.果然Brute Force
!
1 | func strStr(haystack string, needle string) int { |
KMP算法
之前觉得KMP
算法厉害是因为认为它真的很厉害,现在觉得它厉害是因为它的发明者厉害…话不多说,先上代码
1 | func kmpSearch(str, subStr string) int { |
暴力法每次匹配失败后都要重头开始,之前匹配成功的结果被白白浪费很可惜,那么试想一下我们该怎样利用之前已匹配成功的资源?这就是KMP
算法的核心
KMP
算法采用了一个next
数组,这个数组由模式串计算而来,是模式串的最长公用前后缀表.听起来有些复杂,举个例子:
1 | 模式串: A B C D A B D |
那么这个next
数组是怎么产生的呢?我们现在看看A
,由于只有一个字符,它的最长前缀为””,最长后缀为””,最长公用前后缀的长度为0
接下来是AB
,它的最长公用前后缀为””,因此也是0
以此类推…:
字符串 | 最长公用前后缀 | 长度 |
---|---|---|
A | 0 | |
AB | 0 | |
ABC | 0 | |
ABCD | 0 | |
ABCDA | A | 1 |
ABCDAB | AB | 2 |
ABCDABD | 0 |
字符串匹配时有了这样的一个数组,就可以简化算法复杂度
为什么呢?当模式串不匹配时,如果那个不匹配的字符前面的后缀子串有着相同的前缀子串的话,那么直接将模式串移至前缀子串的位置就可以大大缩减匹配时间,next
数组记录的值实际上就是模式串需要移至的值
Sunday算法
Sunday算法是由BM算法改进而来,BM算法是另一个模式匹配算法,暂时不议
Sunday算法是从前往后匹配,在匹配失败时关注的是主串中参加匹配的最末位字符的下一位字符:
- 如果该字符没有在模式串中出现则直接跳过,即移动位数=模式串长度+1
- 否则,其移动位数=模式串长度-该字符最右出现的位置(以0开始)=模式串中该字符最右出现的位置到尾部的距离+1
1 | main string: substring searching |
使用Sunday算法,仅仅匹配错误了两次就成功了,由此可见其高效性
下面是实现的代码:
1 | func sundaySearch(str, subStr string) int { |
用字典存储模式串的值过于消耗空间,由于这里字符就是字节,因此可以用大小为256的数组代替:
1 | func sundaySearch(str, subStr string) int { |
小结
字符串匹配算法有很多种,这里仅仅是列出了比较具有代表性的几种…另外,Sunday算法真的是速度又快有简单,以后谁再跟我提KMP我跟谁急