哈夫曼树(基于优先队列最小堆)

实验三、哈夫曼编码

 

一、实验内容

输入一段文本,计算其中每一个字符的哈夫曼编码,输出编码后文本的长度。

哈夫曼编码作为一种变长编码方式,在文件/图像压缩领域有着重要的应用。

 

二、设计思路

给定n个树叶的权值,改造带权路径总长最短的最优二叉树的算法由哈夫曼给出。

a. 对个权值进行排序,满足

b. 计算作为中间节点的权,的左儿子是,右儿子是. 在权序列中删除, 加入. 若, 结束,否则转a.

       树的一个节点用一个结构体表示,结构体的指针域两个指针分别指向该节点的左儿子和右儿子。如此按哈夫曼算法可以构造哈夫曼树。

       考虑到本题并未要求给出每个字符的哈夫曼编码,而仅要求计算字符串的哈夫曼编码总长度,故可以略去建树过程,用n个树叶构成的数组模拟建树过程中节点的合并过程。

    另外注意到一个树叶节点在最终构建的哈夫曼树中的深度为h, 则该节点的权值在建树过程中(父节点的权值=左儿子节点的权值+右儿子节点的权值)就会被累加h次。因此在建树过程中每次两个子节点合并成父节点时将父节点的权值进行累加,建树完成后就能得到字符串的哈夫曼编码总长度。这样做可以省去建树完成后遍历整棵树求每个字符的编码长度的过程。

       在建树过程中每次要取带权树叶数组的两个最小值,而后还有将合并的父节点重新插入数组,因此采用最小堆来存储带权树叶。从长度为n的无序带权树叶数组构造最小堆的时间复杂度为, 将父节点的权放到数组尾部时维护最小堆的操作时间复杂度为, 这样的操作一共要进行次,所以算法总的时间复杂度为.

       基于最小堆的数据结构,重新叙述哈夫曼算法如下:

a. 由字符串得到带权树叶数组

b. 由带权树叶数组初始化最小堆

c. 将字符串的哈夫曼编码总长ans初始化为0

d. 如果字符串长度为1,ans = 1,返回

e. 否则n-1次循环

       f. 取队首节点(此轮循环最小权值节点),队首节点出队

       g. 再取队首节点(此轮循环次小权值节点),队首节点出队

       h. 将两节点权值相加形成新的节点入队,维护最小堆结构

       i. ans += 两节点的权值之和

j. 返回

 

三、程序编译环境说明

本地编码调试环境:Visual Studio 2012

OJ平台测试环境:C++ (MinGW g++ 4.7.2)

 

四、关键代码分析

C++ STL中优先队列实现最小堆(优先队列默认的是实现最大堆,要实现最小堆需要自己加入std::greater参数)

priority_queue< int, vector, greater > weight_vec;	// 建立最小堆(默认是最大堆)

用优先队列构建最小堆后,每当队列发生出队/入队操作,最小堆的结构会自动维护。所需头文件:

#include
#include		        // std::priority_queue
#include		// std::greater

基于最小堆数据结构的哈夫曼树建树过程:

for (i=0; i

别忘了字符串长度为1的时候要单独计算,否则算出来是0

if (n == 1)														// 只有一个节点,不会进入建树循环
{
	ans = 1;													// 但也要1bit
}

五、实验结果与分析

【算例1】

测试输入:

adacatedecade

 

理论分析:

字符

t

c

d

e

a

出现次数

1

2

3

3

4

哈夫曼码

000

001

01

10

11

哈夫曼树

字符串哈夫曼编码总长=

 

实验结果:29

 

【算例2】

测试输入:a

 

理论分析:

       测试输入只有一个字符,也需要1bit 的0或1表示,因此字符串哈夫曼编码总长为1.

 

实验结果:1

 

六、代码

#include
#include
#include
#include
#include				// std::priority_queue
#include		// std::greater
using namespace std;

typedef char ElemType;

int main()
{
	ifstream fin("input.txt");
	string code;
	fin >> code;													// 读入字符串
	fin.close();
	int i = 0;
	map weight;										// 字母和字母出现的次数
	for (i=0; i::iterator it;
	priority_queue< int, vector, greater > weight_vec;	// 建立最小堆(默认是最大堆)
	for (it=weight.begin(); it!=weight.end(); it++)
	{
		weight_vec.push(it->second);								// 将每个字母出现的次数放在最小堆里
	}
	int n = weight_vec.size();										// 不重复的字符个数
	int ans = 0;													// 哈夫曼编码后字符串的长度
	int a = 0, b = 0;												// a,b: 建树的2个节点的权值
	if (n == 1)														// 只有一个节点,不会进入建树循环
	{
		ans = 1;													// 但也要1bit
	}
	else
	{
		for (i=0; i


你可能感兴趣的:(图论,基础算法)