[数据结构]哈夫曼树和哈夫曼编码、译码(通俗易懂)

一、哈夫曼树的相关的几个名词

1、路径:在一棵树中,一个结点到另一个结点之间的通路,称为路径。如下图根结点到a结点之间的通路就是一条路径。

2、路径长度:在一条路径中,每经过一个结点,路径长度都要加1。如下图根结点到b结点之间所经过的通路数为2,也就是路径长度为2。

3、结点的权:每一个结点都有一个唯一的数值,称为结点的权。如下图结点a的权值为7,b的权值为5。

4、结点的带权路径长度:根结点到结点间的路径长度*结点的权值。如图1结点b的带权路径长度=2*5=10。

5、树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和。如图1的WPL=1*7+2*5+3*2+3*4=35。

二、什么是哈夫曼树?

在权为w1,w2,...,wn的n各叶子结点的所有二叉树中,树的带权路径长度WPL最小的二叉树称为最优二叉树,也叫赫夫曼树或哈夫曼树。

//哈夫曼树的结点结构
typedef struct {
	char ch;///字符结点
	int weight;///字符权值
	int parent;///父母结点
	int lchild;///左孩子在数组位置的下标
	int rchild;///右孩子在数组位置的下标
}hnode;

三、如何构建哈夫曼树?

在构建哈夫曼树时,只需遵循一个原则:权值越大的结点离根结点越近。(权值最大就作为根结点)

1、构建哈夫曼树的过程:

(1)在n个结点权值中选出最小和次最小的结点权值,两结点组成一个新的二叉树,其根结点的权值为左右孩子结点的权值和。

(2)在原有的n个结点权值中删除最小和次最小·,将新权值加入到n-2个权值的行列中。

(3)重复(1)、(2),直到所有结点构成一棵二叉树为止,这棵树就是哈夫曼树。

2、构建哈夫曼树:

哈夫曼树表:

结点i 字符 权值 左孩子 右孩子 双亲结点
0 a 7 -1 -1 6
1 b 5 -1 -1 5
2 c 2 -1 -1 4
3 d 4 -1 -1 4
4   6 2 3 5
5   11 1 4 6
6   18 0 5 -1

 

伪代码实现:

void Great_hfmtree(HfmTree ht[], int n){///创建哈夫曼树,n个结点
	int m1, m2, x1, x2;
	int i, j;
	for (i = 0; i<2 * n - 1; i++){///初始化
		ht[i].weight = 0;
		ht[i].parent = -1;
		ht[i].lchild = -1;
		ht[i].rchild = -1;
	}
	cout<<"<>输入字符结点:";
	for (int i = 0; i<n; i++)
		cin >> ht[i].ch;
    cout<<"<>输入各字符结点对应的权值:";
	for (i = 0; i<n; i++)
		cin >> ht[i].weight;
	for (i = 0; i<n - 1; i++){
		x1 = x2 = MAXV;
		m1 = m2 = 0;
		for (j = 0; j<n + i; j++){///n+i为原来结点+新增结点
			if (ht[j].parent == -1 && hte[j].weight<x1){///找权值次最小结点
				x2 = x1;///记录x1,保证权值最小一定小于于权值次小
				x1 = ht[j].weight;
				m1 = j;///记录权值次最小结点位置
			}
			else if (ht[j].parent == -1 && ht[j].weight<x2){///找权值最小结点
				x2 = ht[j].weight;
				m2 = j;///记录权值最小结点位置
			}
		}
		ht[m1].parent = n + i;
		ht[m2].parent = n + i;
		ht[n + i].weight = ht[m1].weight + ht[m2].weight;///权值最小结点和权值次最小结点之和
		ht[n + i].lchild = m1;
		ht[n + i].rchild = m2;
	}
}

四、哈夫曼编码

哈夫曼编码:一棵哈夫曼树中,规定哈夫曼树的左分支代表0,右分支代表1,则从根结点到每个叶结点所经过的路径分支组成的0和1序列便为该结点对应字符的编码。如下图

代码实现方法①:

void Reverse(char c[])  	//将字符串倒置
{
	int k = 0;
	char temp;
	while (c[k + 1] != '\0')//获取当前字符编码的长度
	{
		k++;
	}
	for (int i = 0; i <= k / 2; i++)//交换位置
	{
		temp = c[i];
		c[i] = c[k - i];
		c[k - i] = temp;
	}
}

void HfmCode(HfmTree ht[], HCode hc[], int n)//从叶子结点到根结点遍历编码
{
	hc = new HCode[n];
	for (int i = 0; i<n; i++)
	{
		hc[i].data = ht[i].ch;
		int ic = i;
		int ip = ht[i].parent;
		int k = 0;
		while (ip != -1)
		{
			if (ic == ht[ip].lchild)   //左孩子标'0'
				hc[i].code[k] = '0';
			else
				hc[i].code[k] = '1';  //右孩子标'1'
			k++;
			ic = ip;
			ip = ht[ic].parent;
		}
		hc[i].code[k] = '\0';
		Reverse(hc[i].code);
	}
	for(int i=0;i<n;i++) cout<<"结点"<<hc[i].data<<"的字符编码:"<<hc[i].code<<endl;
}

代码实现方法②:

typedef char **huffmancode;///定义字符存储编码指针
void Haffmancode(HfmTree ht[], int n, huffmancode &hcode){
	char *cd = new char[n];///申请内存空间,等同char*cd=(char*)malloc(n*sizeof(char));
	hcode = new char *[n + 1];
	cd[n - 1] = '\0';
	int i, j, c, p;
	for (i = 0; i<n; i++){
		int start = n;
		c = i;
		p = ht[c].parent;
		while (p != -1){
			--start;
			if (ht[p].lchild == c) cd[start] = '0';
			else cd[start] = '1';
			c = p;
			p = ht[p].parent;
		}
		hcode[i] = new char[n - start];///分配内存空间或者建字符数组
		strcpy(hcode[i], &cd[start]);///赋予字符串地址,因为编码数组范围在[n-start,n-1].
	}
	for(int i=0;i<n;i++) cout<<"结点"<<hfmtree[i].ch<<"的字符编码:"<<hcode[i]<<endl;
	delete cd;///释放内存空间
}

五、哈夫曼树完整代码的实现

#include<bits/stdc++.h>
using namespace std;
#define MAXV 10000
#define N 10000
typedef struct
{
	char ch;
	int weight;
	int lchild, rchild, parent;
}HfmTree;
typedef struct
{
	char  data;
	char  code[100];
}HCode;
void Great_hfmtree(HfmTree ht[], int n){///创建哈夫曼树,n个结点
	int m1, m2, x1, x2;
	int i, j;
	for (i = 0; i<2 * n - 1; i++){///初始化
		ht[i].weight = 0;
		ht[i].parent = -1;
		ht[i].lchild = -1;
		ht[i].rchild = -1;
	}
	char x,y;
	cout<<"<>输入各字符结点:";
	for (int i = 0; i<n; i++)
        cin >> ht[i].ch;

    cout<<"<>输入各字符对应的权值:";
	for (i = 0; i<n; i++)
		cin >> ht[i].weight;
	for (i = 0; i<n - 1; i++){
		x1 = x2 = MAXV;
		m1 = m2 = 0;
		for (j = 0; j<n + i; j++){///n+i为
			if (ht[j].parent == -1 && ht[j].weight<x1){///找权值最小结点
				x2 = x1;///记录x1,保证权值最小一定大于权值次小
				x1 = ht[j].weight;
				m1 = j;///记录权值最小结点位置
			}
			else if (ht[j].parent == -1 && ht[j].weight<x2){///找权值次最小结点
				x2 = ht[j].weight;
				m2 = j;///记录权值次最小结点位置
			}
		}
		ht[m1].parent = n + i;
		ht[m2].parent = n + i;
		ht[n + i].weight =ht[m1].weight + ht[m2].weight;///权值最小结点和权值次最小结点之和
		ht[n + i].lchild = m1;
		ht[n + i].rchild = m2;
	}
}
void Reverse(char c[])  	//将字符串倒置
{
	int k = 0;
	char temp;
	while (c[k + 1] != '\0')
	{
		k++;
	}
	for (int i = 0; i <= k / 2; i++)
	{
		temp = c[i];
		c[i] = c[k - i];
		c[k - i] = temp;
	}
}

void HfmCode(HfmTree ht[], HCode hc[], int n)      //输出哈弗曼编码
{
	hc = new HCode[n];
	for (int i = 0; i<n; i++)
	{
		hc[i].data = ht[i].ch;
		int ic = i;
		int ip = ht[i].parent;
		int k = 0;
		while (ip != -1)
		{
			if (ic == ht[ip].lchild)   //左孩子标'0'
				hc[i].code[k] = '0';
			else
				hc[i].code[k] = '1';  //右孩子标'1'
			k++;
			ic = ip;
			ip = ht[ic].parent;
		}
		hc[i].code[k] = '\0';
		Reverse(hc[i].code);
	}
	for(int i=0;i<n;i++) cout<<"结点"<<hc[i].data<<"的字符编码:"<<hc[i].code<<endl;
}
void TransCode(HfmTree ht[], HCode hc[], int n, char *s)    //哈夫曼译码
{
	cout << "解码数据为:";
	int i = 2 * (n - 1);      //根结点
	while (*s != '\0')
	{
		if (*s == '0')
			i = ht[i].lchild;
		else
			i = ht[i].rchild;
		if (ht[i].lchild == -1)
		{
			cout << ht[i].ch;
			i = 2 * n - 2;
		}
		s++;
	}
	cout << endl;
}
void menu(){
	cout << "\t\t◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆" << endl;
	cout << "\t\t*    ********哈夫曼编码/译码器********        *" << endl;
	cout << "\t\t*             1.创建哈夫曼树;                 *" << endl;
	cout << "\t\t*             2.进行哈夫曼编码;               *" << endl;
	cout << "\t\t*             3.进行哈夫曼译码;               *" << endl;
	cout << "\t\t*             4.退出;                         *" << endl;
	cout << "\t\t◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆" << endl;
	cout << "\t\t       <注意>'+'代表' ','-'代表'!'          " << endl;
	cout << endl;
}
int main(){
	HfmTree ht[N]={};
	HCode hc[N];
	char s[N];
	int n;
	char x,y;
	menu();
	system("color b0");
	while (true){
		///menu();
		int op;
		cout << "<>请选择你要进行的功能<1.创建,2.编码,3.译码,4.退出>:";
		cin >> op;
		switch (op){
		case 1:{
		          cout<<"<>输入结点个数:";
				   cin>>n;
				   Great_hfmtree(ht,n);
				   printf("创建哈夫曼树成功!\n\n");
				   printf("结点i\t字符\t权值\t左孩子\t右孩子\t双亲结点\n");
				   for (int i = 0; i<2 * n - 1; i++)
					   cout << i << "\t" << ht[i].ch << "\t" << ht[i].weight << "\t" << ht[i].lchild << "\t" << ht[i].rchild << "\t" << ht[i].parent << endl;
				   puts("");
				   break;
		}
		case 2: {
					HfmCode(ht,hc,n);
					puts("");
					break;
		}
		case 3: {
					cout << "输入要进行译码的字符串:";
					cin >> s;
					TransCode(ht, hc, n, s);
					cout<<"译码成功!";
					//cout << a << endl;
					cout << endl;
					break;
		}
		case 4:{
				   cout << "退出成功!" << endl;
				   exit(0);
				   break;
		}
		default:
			break;
		}
	}
}
/*
颜色属性由两个十六进制数字指定 -- 第一个为背景,第二个则为
前景。每个数字可以为以下任何值之一:

    0 = 黑色       8 = 灰色
    1 = 蓝色       9 = 淡蓝色
    2 = 绿色       A = 淡绿色
    3 = 浅绿色     B = 淡浅绿色
    4 = 红色       C = 淡红色
    5 = 紫色       D = 淡紫色
    6 = 黄色       E = 淡黄色
    7 = 白色       F = 亮白色

如果没有给定任何参数,该命令会将颜色还原到 CMD.EXE 启动时
的颜色。这个值来自当前控制台窗口、/T 命令行开关或
DefaultColor 注册表值。

如果用相同的前景和背景颜色来执行 COLOR 命令,COLOR 命令
会将 ERRORLEVEL 设置为 1。

例如: "COLOR fc" 在亮白色上产生亮红色
*/

运行结果如下: 

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