根据教材《数据结构与算法 Python 实现》整理,代码和笔记可前往我的 Github 库下载 isKage/dsa-notes 。【持续更新中,建议 star !】
1 模式匹配
设字符串 P 的长度为 m 。P 的一个子串(substring)P[i:j+1] 是 P 中从第 i 个到第 j 个字符构成的字符串
P 的一个前缀(prefix)为:P[0:i]
P 的一个后缀(suffix)为:P[i:m-1]
**文本的模式匹配(pattern matching)问题:**给定一个文本(text)字符串 T 和一个模式(pattern)字符串 P ,在 T 中找到一个与 P 一致的子串。
1.1 穷举
蛮力法/穷举法(brute-force)即列举所有可能的情况,并检查是否有符合要求的情况或寻找最优情况。模式匹配中,蛮力法即考虑所有 P 与 T 的子串可能匹配上的情况。
具体操作:将 P 与 T 从头部对齐开始,每一步把 P 相对于 T 向后移动一位,直到找到一个匹配的位置或所有 P 与 T 的相对位置均被探索过。
蛮力法的时间复杂度为 O(nm) ,其中 n 为 T 的长度,m 为模式 P 的长度。
蛮力法的最坏情况例如:T = "aaaaaaaah"P = "aaah"
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deffind_brute(P, T): """ 穷举法模式匹配 :param P: 寻找的模式 :param T: 被查找的对象 :return: T 的索引位置, 若失败返回 -1 """ n, m = len(T), len(P) for i inrange(n - m + 1): k = 0 while k < m and T[i + k] == P[k]: k += 1# 当前匹配成功, 继续匹配 if k == m: return i # 完整匹配, 返回位置 return -1
defcompute_kmp_fail(pattern): """计算模式的失败函数""" m = len(pattern) fail = [0] * m j = 1 k = 0 while j < m: if pattern[j] == pattern[k]: fail[j] = k + 1 j += 1 k += 1 elif k > 0: k = fail[k - 1] else: j += 1 return fail
deffind_kmp(pattern, text): """ 从 text 中找到第一个完全匹配 pattern 的位置 :param pattern: 寻找的模式 :param text: 被查找的对象 :return: text 的索引位置, 若失败返回 -1 """ m, n = len(pattern), len(text) if m == 0: return0
fail = compute_kmp_fail(pattern) j = 0 k = 0 while j < n: if text[j] == pattern[k]: if k == m - 1: return j - m + 1 j += 1 k += 1 elif k > 0: k = fail[k - 1] else: j += 1 return -1
性能分析
失败函数:匹配成功 i 加一,匹配失败则 i or j 至少加一,最多 2m 次操作,复杂度为 O(m) ,m 为模式长度。
KMP 算法:匹配成功 i 加一,匹配失败则 i or j 至少加一,最多 2n 次操作,加之失败函数的复杂度,KMP 算法的复杂度为 O(n + m) ,m 为模式长度,n 为被匹配字符串长度。
2 文本压缩与贪心算法
**文本压缩(text compression)问题:**将给定字符串 X 压缩为一个更小的二进制字符串 Y 。
defencode(self): """编码: text 编码后的结果""" return''.join(self.code_map[ch] for ch inself.text)
defdecode(self, encoded_text): """解码: encoded_text 解码后的结果""" result = [] node = self.root for bit in encoded_text: node = node.left if bit == '0'else node.right if node.char isnotNone: result.append(node.char) node = self.root return''.join(result)
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
if __name__ == "__main__": text = "this is an example of a huffman tree"
对于字符集合 C={c1,c2,⋯,cn} 每个字符出现的频率为 f(ci) 。构造编码树 T 是一个二叉树,叶子节点代表字符,路径代表 0 (Left) or 1 (Right) 。则字符 ci 的编码长度 l(ci) 为 ci 所在节点在 T 中的深度。那么对这个字符集 C 的编码树 T 带来的期望编码总长为:
L(T)=i=1∑nf(ci)⋅l(ci)
现在要证明:Huffman 编码树 TH 就是最优的,它使得 L(TH) 最小。
引理 1 在最优编码树 T∗ 中,频率最低的 2 个字符 x 和 y 一定位于最深的节点中,且他们拥有相同的父节点。
证明 引理 1 若字符 z 比 x 更深,但 x 频率更低 f(x)≤f(z) 。那么交换 x 和 z 的节点位置,总编码期望长度 L(T) 不增(因为频率低的字符变得更深)。这与 L(T∗) 最优矛盾。同理,存在 2 个频率最低的字符 x,y 那么他们肯定在同一层,否则将频率低的换到更深,长度不增,矛盾。□