Huffman Codes
- 二位码:将字符表中的字符映射为二位编码
- 定长编码:易于设计,但是空间浪费严重——压缩编码?
- 变长压缩编码:存在歧义问题,界限不确定——消歧?
- 消歧方案——变长无前缀码:没有任何一个码字是另外一个码字的前缀
- 词频越高,对应的码字越短——平均码字长度更短
- 目标:给定一组字符及其频率,给出最佳的非前缀编码——编码树
- 无前缀编码树:左子节点对应0,右子节点对应1
- 只有叶子节点有明确的字符对应
- 编码——根节点到对应叶子节点的位路径
- 解码——沿着树向下遍历,到达叶子节点即获得对应字符
- 编码长度即树的深度
- 问题定义
- 输入:一组字符的个字符概率(出现频率)
- L ( T ) L(T) L(T):平均编码长度
- 输出:一棵最小化平均编码长度的编码树
- 构建树的贪心算法:自底向上的方法
- 不断地合并节点
- 产生 N − 1 N - 1 N−1个内部无标记节点,同时保证所有的叶子节点都是标记节点
- “安全”的合并——霍夫曼算法
- 对于字符 i i i的编码长度,等于其被合并的次数
- 选择出现频率最低的两个字符进行合并
- 合并后的中间节点(子树)的对应频率为所有标记节点频率的总和
- 统一采用右倾二叉树的形式
- 时间复杂度:堆实现
O
(
n
log
n
)
O(n \log n)
O(nlogn)
- 每次取最小的两个
- 合成新节点(键值为求和)重新插入到堆中
Dynamic Programming
- 路径图的加权独立子集问题
- 问题定义
- 输入:一个路径图 G = ( V , E ) G=(V, E) G=(V,E),各节点具有非负权重
- 输出:非邻接节点子集——独立集,具有最大权重和
- 最优子结构
- 可以缩小候选集,并在候选集中直接进行暴力搜索
- 记 S ⊆ V S \subseteq V S⊆V为WIS, v n v_n vn为最后一个节点
- 考虑 v n v_n vn是否属于 S S S的两种情形下的最优结果
- v n ∉ S v_n \notin S vn∈/S,那么除去之的子图 G ′ G^\prime G′, S S S仍为其最大权重WIS
- v n ∈ S v_n \in S vn∈S,那么连接之的 v n − 1 v_{n - 1} vn−1不可选择,除去这两个点的子图 G ′ ′ G^{\prime\prime} G′′,除去 v n v_n vn的 S ′ S^\prime S′仍然为最优WIS
- 最优结果搜索
- 暴力搜索?指数时间复杂度
- 实际每一次迭代考虑的子问题只涉及当前子图下最右节点是否不选择—— O ( n ) O(n) O(n)个子问题
- 时间复杂度简化——对于已经解决的子问题,结果保存到一个全局数组中——memoization记忆化
- 通过记忆化+bottom-up迭代方式(从最左节点开始)实现算法的线性时间复杂度
- 算法
- 初始化 A [ 0 ] = 0 A[0] = 0 A[0]=0, A [ 1 ] = w 1 A[1] = w_1 A[1]=w1
- 主循环:对
i
=
2..
n
i = 2 .. n
i=2..n
- A [ i ] = max { A [ i − 1 ] , A [ i − 2 ] + w i } A[i] = \max \{A[i - 1], A[i - 2] + w_i\} A[i]=max{A[i−1],A[i−2]+wi}
- 分别对应选还是不选当前节点两个情形,取最优
- 时间复杂度 O ( n ) O(n) O(n)
- 问题:算法给出了最优子结构,但是如何选择最后的答案?路径重建
- 重建算法
- 除了保存最优值,还要保留选择最优值时的最优节点选择方案
- 从后往前看
- 如果 A [ i − 1 ] ≥ A [ i − 2 ] + w i A[i - 1] \ge A[i - 2] + w_i A[i−1]≥A[i−2]+wi,跳过之,继续往前看
- 否则,将当前节点加入到结果中,继续往前看
- 时间复杂度 O ( n ) O(n) O(n)
- 问题定义
- 动态规划原则
- 明确一系列子问题——最优子结构
- 再给定一个小的子问题的答案之后,能够快速且正确地计算更大的子问题——递归 / 循环自由子结构状态变更
- 在完成所有子问题的计算后,整个问题最终求解可以很容易得到