算法笔记---自底向上的赫夫曼编码

题目描述

在通讯领域,经常需要将需要传送的文字转换成由二进制字符组成的字符串。在实际应用中,由于总是希望被传送的内容总长尽可能的短,如果对每个字符设计长度不等的编码,且让内容中出现次数较多的字符采用尽可能短的编码,则整个内容的总长便可以减少。另外,需要保证任何一个字符的编码都不是另一个字符的编码前缀,这种编码成为前缀编码。
而赫夫曼编码就是一种二进制前缀编码,其从叶子到根(自底向上)逆向求出每个字符的算法可以表示如下:

在这里插入图片描述

在本题中,读入n个字符所对应的权值,生成赫夫曼编码,并依次输出计算出的每一个赫夫曼编码。

输入:

输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。

输出:

共n行,每行一个字符串,表示对应字符的赫夫曼编码。

样例:

输入:
8
5 29 7 8 14 23 3 11
输出:
0110
10
1110
1111
110
00
0111
010

解题思路:
1、结构体中包含权值、左子树、右子树、父结点
2、初始化时,所有结点的父节点设置为0,权值按照输入的权值进行赋值
3、编写获取集合中最小的两个元素的下标。
4、构造哈夫曼树
5、得出哈夫曼编码
6、打印输出

注意:
该题与 算法笔记—自顶向下的赫夫曼编码 区别不同在于编码的方式不同
此外,该题使用vector数据结果来存放哈夫曼编码,数组下标是从 0 开始。
当然读者可以设置为从 1 开始,这样更加方便。例如在编写 算法笔记—自顶向下的赫夫曼编码 的代码中,数组的下标博主就是从 1 开始的,这样便于书写构造哈夫曼编码的代码。

下面为AC代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<limits.h> //INT_MAX的头文件,不包含编译可能不会通过
using namespace std;

struct huffman_node {
	int weight;//权值
	int lchild, rchild;//左右子树
	int parent;//父结点
};


//************************************
// Method:    select_tow_min 找出集合中两个最小的值
// FullName:  select_tow_min
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: vector<huffman_node> & huffman_tree 存放哈夫曼树的集合
// Parameter: int n 当前哈夫曼树共有多少结点
// Parameter: int & first_min 第一个最小结点
// Parameter: int & second_min 第二个最小结点
//************************************
void select_tow_min(vector<huffman_node>& huffman_tree, int n, int &first_min, int &second_min)
{
	int min_value = INT_MAX;
	for (int i = 0;i < n;i++)//找出第一个最小结点
	{
		if (huffman_tree[i].parent == 0 && huffman_tree[i].weight < min_value)
		{
			min_value = huffman_tree[i].weight;
			first_min = i;
		}
	}

	min_value = INT_MAX;

	for (int i = 0;i < n;i++)//找出第二个最小结点
	{
		if (huffman_tree[i].parent == 0 && huffman_tree[i].weight < min_value && i != first_min)
		{
			min_value = huffman_tree[i].weight;
			second_min = i;
		}
	}
	if (first_min > second_min)
	{
		swap(first_min, second_min);//保证编码唯一。因为给定题目是按照数字下标来区分左右子树的。
	}
}

//************************************
// Method:    huffman_solve 求出哈夫曼编码
// FullName:  huffman_solve
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: vector<huffman_node> & huffman_tree 存放哈夫曼树的结点
// Parameter: vector<string> & code 存放哈夫曼编码
// Parameter: vector<int> & weight 各个输入结点的权值
// Parameter: int n 输入结点的总个数
//************************************
void huffman_solve(vector<huffman_node> & huffman_tree, vector<string>& code, vector<int> &weight, int n) {
	if (n <= 1)
	{
		return;
	}
	int m = n * 2 - 1;//哈夫曼树的所有结点数目
	huffman_tree.resize(m);

	//初始化
	for (int i = 0;i < n;i++)
	{
		huffman_tree[i].weight = weight[i];
		huffman_tree[i].parent = huffman_tree[i].lchild = huffman_tree[i].rchild = 0;
	}
	for (int i = n;i < m;i++)
	{
		huffman_tree[i].parent = huffman_tree[i].lchild = huffman_tree[i].rchild = 0;
	}

	//构造哈夫曼树
	for (int i = n;i < m;i++)
	{
		int first_min, second_min;
		select_tow_min(huffman_tree, i, first_min, second_min);//找出两个最小值
		huffman_tree[i].lchild = first_min;//左子树
		huffman_tree[i].rchild = second_min;//右子树
		huffman_tree[first_min].parent = huffman_tree[second_min].parent = i;//父结点
		huffman_tree[i].weight = huffman_tree[first_min].weight + huffman_tree[second_min].weight;//父结点的 weight
	}

	//求出每个叶节点的编码
	for (int i = 0; i < n; i++) {
		string str = "";
		int child = i;//当前结点为孩子结点
		int parent = huffman_tree[i].parent;//当前结点的父节点
		do 
		{
			if (huffman_tree[parent].lchild == child)
			{
				str = '0' + str;
			}
			else
			{
				str = '1' + str;
			}
			child = parent;
			parent = huffman_tree[child].parent;
		} while (parent != 0);//自底向上求出哈夫曼编码

		code.push_back(str);
	}
}

int main() {

	int n;
	cin >> n;
	vector<int> v;
	int data;
	for (int i = 0;i < n; i++)
	{
		cin >> data;
		v.push_back(data);
	}

	vector<huffman_node> huffman_tree;//哈夫曼树的结点集合
	vector<string> code;//哈夫曼编码

	huffman_solve(huffman_tree, code, v, n);
	for (vector<string>::iterator it = code.begin(); it != code.end(); it++) {
		cout << (*it) << endl;
	}
	//system("pause");
	return 0;
}

下面为输出结果:

自底向上运行结果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章