实现霍夫曼编码算法
哈夫曼编码是一种压缩技术,用于减少发送或存储信息或字符串所需的比特数。它基于这样一种理念:频繁出现的字符将具有较短的比特表示,而较少出现的字符将具有较长的比特表示。如果我们把哈夫曼编码看作一个树形结构,那么出现频率较低的字符或项目将位于树的顶端,而出现频率较高的项目将位于树的底部或树叶中。哈夫曼编码在很大程度上依赖于优先级队列。要计算哈夫曼编码,首先要创建一棵节点树。
创建节点树的过程:
-
我们必须为每个符号创建一个叶节点并将其添加到优先级队列中。
-
当队列中有多个节点时,请执行以下操作:
-
将优先级最高(概率/频率最低)的节点删除两次,得到两个节点。
-
创建一个新的内部节点,将这两个节点作为子节点,其概率/频率等于这两个节点的概率/频率之和。
-
将新节点添加到队列中。
-
-
剩下的节点就是根节点,树就完整了。
然后,我们必须从树根到树叶遍历所构建的二叉树,在每个节点为一个分支分配和累积一个 "0",为另一个分支分配和累积一个 "1"。每片叶子上累积的 0 和 1 就构成了这些符号和权重的哈夫曼编码。下面是利用 SPL 优先级队列实现的哈夫曼编码算法:
Unresolved include directive in modules/ROOT/pages/ch11/ch11-05.adoc - include::example$Chapter11/4.php[]
在这里,我们为每个符号建立一个最小堆,并使用它们的权重来设置优先级。一旦堆构建完成,我们就会一个接一个地提取两个节点,然后结合它们的数据和优先级,将其添加回堆。除非只存在一个节点,也就是根节点,否则这个过程会一直持续下去。现在,让我们运行以下代码来生成哈夫曼代码:
Unresolved include directive in modules/ROOT/pages/ch11/ch11-05.adoc - include::example$Chapter11/4.php[]
在这里,我们使用 str_split
将字符串分解为数组,然后使用数组计数值将其转换为关联数组,其中字符是键,字符在字符串中出现的次数是值。前面的代码将产生以下输出:
Symbol Weight Huffman Code
i 1 00000
D 1 00001
d 1 00010
A 1 00011
t 4 001
H 1 01000
m 1 01001
P 2 0101
g 1 01100
o 1 01101
e 1 01110
n 1 01111
7 1 10000
l 1 10001
u 2 1001
5 101
h 1 11000
c 1 11001
a 3 1101
r 3 1110
s 3 1111
贪心算法还有许多其他实际用途。我们将用贪心算法解决一个工作调度问题。让我们以一个敏捷软件开发人员团队为例,他们正在进行为期两周的迭代或冲刺。他们有一些用户故事要完成,任务有一些截止日期(按日期),故事有一些速度(故事的大小)。团队的目标是在给定的期限内获得冲刺的最大速度。让我们考虑以下带有截止日期和速度的任务:
Index |
1 |
2 |
3 |
4 |
5 |
6 |
Story |
S1 |
S2 |
S3 |
S4 |
S5 |
S6 |
Deadline |
2 |
1 |
2 |
1 |
3 |
4 |
Velocity |
95 |
32 |
47 |
42 |
28 |
64 |
从上表中可以看出,我们有 6 个用户故事,它们的截止日期从 1 到 4 有 4 个不同的时间段。 我们必须在第 1 个时间段完成用户故事 S2 或 S4,因为该任务的截止日期是 1。 故事 S1 和 S3 也是如此,它们必须在第 2 个时间段或之前完成。但是,由于我们有 S3,而且 S3 的速度大于 S2 和 S4,因此贪婪法将选择 S3 作为第 1 个时隙。让我们来编写计算速度的贪心代码:
Unresolved include directive in modules/ROOT/pages/ch11/ch11-05.adoc - include::example$Chapter11/5.php[]
在这里,我们将获得作业列表(用户故事 ID、截止日期和速度),并利用这些信息找出最大速度及其各自的用户故事 ID。首先,我们使用自定义用户排序功能 usort
对作业数组进行排序,并根据它们的速度按降序排序。然后,我们从截止日期列计算可用槽位的最大数量。然后,我们将槽数组初始化为-1,以保留已用槽的标记。下一个代码块将遍历每个用户故事,并为用户故事找到合适的时隙。如果可用时隙已满,我们就不再继续。现在,让我们使用下面的代码块运行这段代码:
Unresolved include directive in modules/ROOT/pages/ch11/ch11-05.adoc - include::example$Chapter11/5.php[]
这将在命令行中产生以下输出:
Stories to Complete: S3 S1 S5 S6
Max Velocity: 234
贪婪算法有助于解决局部优化问题,如工作调度、网络流量控制、图算法等。然而,要获得全局优化的解决方案,我们需要关注算法的另一个方面,即动态编程。