字符串匹配(上)
- BF算法:一种暴力匹配的算法。
- RK算法:基于BF算法的改进,利用了哈希算法。
- BM算法:
- KMP算法:
这四种都是单模式匹配的算法,就是一个字符串跟另一个字符串匹配。
BF算法
BF: Brute Force的缩写
中文名:暴力匹配算法、朴素匹配算法
时间复杂度:O(n*m)
示例: 在字符串A中查找字符串B,那么字符串A就是 主串 ,字符串B就是 模式串 ,主串长度记作n,模式串长度记作m。
- BF算法: 在主串中,检查起始位置分别是0、1、2…n-m且长度为m的
n-m+1
个子串,看有没有跟模式串匹配的。 - 时间复杂度: 每次对比m个字符串,要对比
n-m+1
次,最坏时间复杂度是O(n*m)
- 优点: 虽然
BF
算法时间复杂度高,但是实际开发中,却是常用的字符串匹配算法。- 实际开发中,模式串和主串长度不会太长,并且每次模式串与主串的子串匹配时,途中遇到不能匹配的字符时,就可以停止,不需要完全对比。虽然理论时间复杂度
O(n*m)
大部分情况效率高很多。 - 算法简单,代码实现非常简单,在满足性能的前提下,简单是首选。
- 实际开发中,模式串和主串长度不会太长,并且每次模式串与主串的子串匹配时,途中遇到不能匹配的字符时,就可以停止,不需要完全对比。虽然理论时间复杂度
def bf(main, pattern):
"""
字符串匹配,bf暴力搜索
main:主串
pattern:模式串
"""
n, m = len(main), len(pattern)
if n <= m:
return 0 if pattern == main else -1
for i in range(n - m + 1):
for j in range(m):
if main[i + j] == pattern[j]:
if j == m - 1:
return i
else:
continue
else:
break
return -1
if __name__ == '__main__':
res = bf('i like apple', 'ke')
print(res)
RK算法
RK:Rabin-Karp算法
BF算法的升级版,利用了哈希算法,减少了模式串和子串的比较时间。
时间复杂度 理想:O(n) 最差:O(n*m)
在BF算法时,我们需要暴力的对比 n-m+1
个子串与模式串,找出主串与模式串匹配的子串。
每次检查主串与子串是否匹配,需要依次对比每个字符,BF算法的时间复杂度比较高是 O(n*M)
。
优化: 我们队朴素字符串匹配算法改进,引入哈希算法。
RK算法: 通过哈希算法对主串的 n-m+1
个字串求哈希值,然后逐个与模式串的哈希值比较大小。若某个子串的哈希值与模式串相等,就说明匹配成功(不考虑哈希冲突问题),数字之间的比较非常快,所以模式串和子串比较的效率就提高了。
缺点: 计算哈希值需要遍历子串中的每个字符,尽管模式串与子串比较效率提高了,算法的整体效率没提高。
优化: 比如要处理的字符串只包含 a~z 这 26 个小写字母,那我们就用26进制来表示一个字符串。
时间复杂度: 只需要扫描一遍主串计算出所有子串的哈希值,这部分时间复杂度 O(n)
,模式串和每个子串哈希值比较时间复杂度 O(1)
,总共比较 n-m+1
个子串的哈希值,这部分时间复杂度 O(n)
,RK算法的整体时间复杂度 O(n)
。
问题: 当模式串很长,对应的子串也会很长,得到的哈希值就会很大。刚刚的优化方法是没有散列冲突的,就是一个字符串与一个26进制数一一对应。当我们按a~z对应的26个数字来进行相加,产生的哈希冲突会很高。
解决问题: 我们可以判断两个哈希值如果相同,我们再去对比下子串和模式串本身。当存在大量冲突会退化成 O(n*m)
,一般情况下 RK 比 BF 算法高。
def simple_hash(s, start, end):
"""
计算子串哈希值
每个字符取acs-ii码后求和
"""
assert start <= end
ret = 0
for c in s[start:end+1]:
ret += ord(c)
return ret
def rk(main, pattern):
"""
rk匹配
main:主串
pattern:模式串
"""
n, m = len(main), len(pattern)
if n <= m:
return 0 if pattern == main else -1
# 子串哈希值表
hash_memo = [None] * (n-m+1)
hash_memo[0] = simple_hash(main, 0, m-1)
for i in range(1, n-m+1):
hash_memo[i] = hash_memo[i - 1] - simple_hash(main, i-1, i-1) + simple_hash(main, i+m-1, i+m-1)
# 模式串哈希值
hash_p = simple_hash(pattern, 0, m-1)
for i, h in enumerate(hash_memo):
if h == hash_p:
if pattern == main[i:i+m]:
return i
else:
continue
return -1
if __name__ == '__main__':
res = rk('i like apple', 'ke')
print(res)