字符串两种前缀算法(z和kmp)
KMP字符串是数据结构课本上教过的,其实就是类似某种DP算法: `dp[i] = sz` 表示 `s[i-sz..i] = s[0..sz]` 基于KMP算法可以扩展出AC自动机算法:基于BFS算法来构建这个dp, 在AC里面这个dp叫做fail指针。在KMP里面也可以把这个叫做fail指针。KMP算法的好处就是可以只针对pattern做index, 这个和后面Z函数形成对比:Z函数如果要求解前缀的话需要同时对hay和needle同时做索引。
class KMP2: @staticmethod def build_fail(t): n = len(t) fail = [-1] * n p = -1 for i in range(1, n): while t[i] != t[p + 1]: if p == -1: break p = fail[p] if t[i] == t[p + 1]: p += 1 fail[i] = p return fail def __init__(self, t): self.t = t self.fail = self.build_fail(t) def search(self, s): fail, t = self.fail, self.t j = -1 for i in range(len(s)): while s[i] != t[j + 1]: if j == -1: break j = fail[j] if s[i] == t[j + 1]: j += 1 if (j + 1) == len(t): return i + 1 - len(t) return -1
另外一个前缀算法是Z函数,其中 `z[i]=sz` 表示从 i 开始最多可以和 0 开始匹配sz个字符串。 这里和KMP不同:KMP是知道当前位置结尾最多匹配多少,而Z函数是知道当前位置起始最多可以匹配多少。两种不同的语义都可以有效地求解出来。
def compute_z(s): n = len(s) z = [0] * n l, r = 0, 0 for i in range(1, n): if i <= r: # s[l..r] = s[0..r-l] # s[i..r] = s[i-l..r-l] # 所以z[i-l]是从s[i]作为起点的最大匹配距离 # 但是这个匹配距离也收到r的限制. z[i] = min(z[i - l], r - i + 1) while i + z[i] < n and s[z[i]] == s[i + z[i]]: l, r = i, i + z[i] z[i] += 1 return z def index(s, p): m = len(p) z = zfunction(p + '$' + s) for i in range(m + 1, len(s) + len(p) + 1): if z[i] == m: return i - m - 1 return -1