HuffmanTree哈夫曼树(赫夫曼树)及哈夫曼编码

今天带领你们学一下哈夫曼ios

一. 概念:

赫夫曼树又叫作最优二叉树,它的特色是带权路径最短数组

1)路径:路径是指从树中一个结点到另外一个结点的分支所构成的路线,编码

2)路径长度:路径长度是指路径上的分支数目。spa

3)树的路径长度:树的路径长度是指从根到每一个结点的路径长度之和。翻译

4)带权路径长度:结点具备权值,从该节点到根之间的路径长度乘以结点的权值,就是该结点的带权路径长度。code

5)树的带权路径长度(WPL)树的带权路径长度是指树中全部叶子结点的带权路径长度之和blog

举个例子 a  b  c  d 四个结点,权值分别为 7,5,2,4图片

如图所示二叉树的4个叶子结点。a根结点的分支数目为2,a的路径长度为2,a 的带权路径长度为  7*2 = 14ip

一样  b 的带权路径长度为  5 * 2 = 10。c , d  的分别是   3 * 2 = 6,    4 * 2 = 8。                          ci

这棵树的带权路径长度为  WPL = 8 + 6 + 10 + 14.

二. 赫夫曼树的构造方法

仍是上面的例子把

a  b  c  d 四个结点,权值分别为 7,5,2,4

 

(1)咱们选出权值最小的两个根c和d,做为左子树和右子数  构成一棵二叉树,新二叉树的根结点权值为c和d权值之和。删出c和d。同时将新构造的二叉树加入集合点。以下图

 

继续从  选择权值最小的两个根,即权值为  5和6 的两个结点,而后以它们做为左右子树构造一个新的二叉树。新的二叉树的权值为  5 + 6 = 11,删除  权值为  5和6 的两棵树,将新构造权值为11的树加如集合。

 

继续从选择权值最小的 直到全部集合中只剩下一棵二叉树。

此时集合中只剩下这一刻二叉树,这棵树就是赫夫曼树,至此赫夫曼树的构造结束,计算值为WPL = 7*1 + 5*2 + 2*3 + 4*3 = 35

在以a b c d这4个结点为叶子结点的全部二叉树中,赫夫曼树的WPL最小

赫夫曼树的特色

1)权值越大的结点,距离根节点越近。

2)树种没有度为 1 的结点,这类树有叫作 正则(严格)二叉树。

3)树的带权路径长度最短

4)赫夫曼树不惟一

三. 赫夫曼编码

前面关于赫夫曼树的讲解中 屡次提到最,最优二叉 最短带权路径长度。因此说咱们的赫夫曼树必定是有很大的做用的。好比在存储文件的时候,对于包含同一内容的文件有多种存储方式,是否是能够找出一种最节省空间的存储方式呢?答案是确定的,否则我就不会问。这就是赫夫曼编码的用途,常见的zip压缩文件和.jpeg图片文件的底层技术都用到了赫夫曼编码。

如今给你们举一个简单的例子来讲明赫夫曼编码如何实现对文件进行压缩储存的,如这样一串字符“S = AAABBACCCDEEA” 选用3位长度的二进制为各个字符编码(二进制数位随意,只要够编码全部不一样的字符便可)

这是咱们使用常规编码后,每一个字符的编码号

根据该表咱们能够把S串编码为:

T(S)= 000000000001001000010010010011100100000   该串长度为  39,那有没有办法使这个编码串的长度变小呢?而且能准确解码获得原来的字符串?下面咱们用赫夫曼树试一试。

First  step:  咱们先统计各个字符出现的次数。

A  5次   ,B  2次  , C  3次,D   1次,E   2次

Second:  咱们以字符为结点,以出现的次数为权值,构造赫夫曼树。

对应赫夫曼树的每一个结点的左右分支进行编号,从根结点开始,向左为 0 向右为 1 。以后从根到每一个结点路径上的数字序列即为每一个字符的编码,

  

A~E的赫夫曼编码为
A B C D E
0 110 10 1110 1111

怎S串的编码为H(S) = 00011011001010101110111111110

上述由赫夫曼树导出每一个字符的编码,进而获得对整个字符串的编码过程称为赫夫曼编码。H(S)的长度为29,必T(S)短了不少,前面每三位表明一个字符,解码时每三位解一次就能够得出源码,而如今赫夫曼解码确是不定长的,不一样的字符可能出现不一样的编码串,这样若是出现一个字符的编码串是另外一个字符串编码的前缀,岂不是会出现解码的歧义?例如 A的编码是0 B的是00  那对于 00这个编码是 AA呢仍是B呢?这个问题能够用前缀码来解决,即任何一个字符的编码都不是另外一个字符编码的前缀。而赫夫曼编码产生的就是前缀码 ,由于被编码的字符都处与叶子结点上,而根通往任何一叶子结点的路径都不多是通往其他叶子结点路径的子路径。所以任何一编码串的子串不多是其余编码串的子串

其实 你们随便画一棵二叉数也能构造出前缀码,为何非要用赫夫曼树呢?

由赫夫曼树的特性可知,其树的带权路径的长度最短。赫夫曼编码过程当中,每一个字符的权值是在字符串中出现的次数,出现的次数越多,距离根节点就越近,每一个字符的编码长度就是这个字符的路径长度。所以获得的整个字符串的长度必定是最短的,结论赫夫曼编码产生的就是最短前缀码

四. 赫夫曼n叉树

赫夫曼可不止二叉树哦,

赫夫曼二叉树只是赫夫曼树的一种。

咱们怎么去构造一个赫夫曼n叉树呢?  来举个栗子, 

序列A    B    C     D   权值分别为   1  3  4  6   怎么构造一个三叉树呢?   这时咱们发现没法直接构造,咱们须要补上权值为0的结点让整个序列能够凑成构造赫夫曼3插树的序列,E 的权值为 0, 而后相似二叉树的构建方法。每次挑权值最小的三个结点构造一棵三叉树,以此类推,直至集合中全部结点都加入树中。

 重点:当发现没法构造时,(1),补权值为0的结点让整个序列能够凑成构造赫夫曼n插树的序列。(2)相似构造赫夫曼二叉树的方法构造n叉树,直至全部结点都加入树中

 

赫夫曼树的编码 代码 解码 建树过程

如今让咱们一步一步构思 

从最简单的开始,先给你 A B C D  4个节点 权值分别为  5   4   2    7

把他们一块儿存到一个数组内,而后  遍历 找到2个权值最小的 节点相加而后加入数组,标记一下,这两个及节点就被删除了,两个节点相加获得新的节点,加入到数组中,以后再继续 遍历 找到权值最小的两个点,继续重复上述操做,直到全部前面的  最初的 4 个节点都用过。(下面数组咱们对第0列弃用,方便填下标)

下标 0 1 2 3 4 5 6 7
weight 0 5 4 2 7 0 0 0
parent 0 0 0 0 0 0 0 0
lchild 0 0 0 0 0 0 0 0
rchild 0 0 0 0 0 0 0 0

咱们进行第一次遍历  找到最小的两个数  4   2   而后让他们做为左右子树构形成一个 二叉树  将这个 新的权值 2+4 = 6填入到下标第一空的地方,将 2 和 4 的parent 改成  新建权值的下标  将 新建节点的左右子树 改成两个节点的 下标

下标 0 1 2 3 4 5 6 7
weight 0 5 4 2 7 6 0 0
parent 0 0 5 5 0 0 0 0
lchild 0 0 0 0 0 2 0 0
rchild 0 0 0 0 0 3 0 0

以后继续遍历 weight  从第1个  到  第5个 找到  权值最小的两个(2 和 4已经被用过了)找到 5 和 6,继续建树 。新建树的权值为 11  ,而后改 5  和 6  的parent 为 新建权值的下标  6.改新建权值的  lchild为  1 。rchild 为  5。

下标 0 1 2 3 4 5 6 7
weight 0 5 4 2 7 6 11 0
parent 0 6 5 5 0 6 0 0
lchild 0 0 0 0 0 2 1 0
rchild 0 0 0 0 0 3 5 0

而后咱们继续遍历找到  7 和 11 继续建树,加到数组的最后一个位置 改 7 和 11 的parent 改成 7,    新建节点18 的lchild 为 权值为7的下标4  .新建节点18 的 rchild 为权值为11 的下标6.这时全部的节点都用完了,咱们构建赫夫曼树完成。

下标 0 1 2 3 4 5 6 7
weight 0 5 4 2 7 6 11 18
parent 0 6 5 5 7 6 7 0
lchild 0 0 0 0 0 2 1 4
rchild 0 0 0 0 0 3 5 6

而后求 每一个字符的Huffman编码。

咱们从举个例子,先找第一个 字符的huffman编码,也就是权值为 5 的赫夫曼编码,

1)先构建一个栈。

2)而后找第一个字符的  parent,判断是lchild  仍是 rchild。(parent  是  6)找到下标为6的列

3)判断是lchild  仍是 rchild。若是是lchild  则向栈中 压入0,若是是rchild 则向栈中压入1.(左孩子,把0压入栈中)

4)而后继续找parent。(找到6(权值11)的parent,下标为 7),重复操做 (3)操做(4)直到 Parent 变为0结束(也就是根节点)。而后弹栈,用一个数组接收。

而后咱们求出第一个字符的 赫夫曼编码是  10

 

下面给个题:

给定报文中26个字母a-z及空格的出现频率{64, 13, 22, 32, 103, 21, 15, 47, 57, 1, 5, 32, 20, 57, 63, 15, 1, 48, 51, 80, 23, 8, 18, 1, 16, 1, 168},构建哈夫曼树并为这27个字符编制哈夫曼编码,并输出。模拟发送端,从键盘输入字符串,以%为结束标记,在屏幕上输出输入串的编码;模拟接收端,从键盘上输入0-1哈夫曼编码串,翻译出对应的原文

 

#include<iostream>
#include<stdlib.h>
#include<stack>
#include<cstring>
#include<string.h>
using namespace std;

typedef struct 
{
	int weight;
	int parent, lchild, rchild;
}HTNode, *HuffmanTree;
typedef char **HuffmanCode;


void Select(HuffmanTree &HT, int end, int &s1, int &s2)		//找出最小的两个值。两个最小的值得下标记录到 s1 s2中。 
{
	int min1 = 0x3f3f3f, min2 = 0x3f3f3f; 
	for(int i = 1; i <= end; i++)
	{
		if(HT[i].parent == 0 && HT[i].weight < min1)
		{
			min1 =  HT[i].weight;
			s1 = i;		
		}
	}
	for(int i = 1; i <= end; i++)
	{
		if(HT[i].parent == 0 && HT[i].weight < min2 && s1 != i)
		{
			min2 = HT[i].weight;
			s2 = i;
		}
	}
}

void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n) //建立赫夫曼树和赫夫曼编码 
{

	int i, s1, s2; 
	HuffmanTree p;
	if(n <= 1)
	return ;
	int m = 2 * n - 1;
	
	HT = (HuffmanTree) malloc((m + 1) * sizeof(HTNode));
	for(p = HT + 1, i = 1; i <= n; i++, p++, w++)
	{
		*p = {*w, 0, 0, 0};
	}
	 
	 	
	 for(; i <= m; i++, p++)
	 *p = {0, 0, 0, 0};

	 for(i = n + 1; i <= m; i++)
	 {
	 
	 	Select(HT, i-1, s1, s2);
	 	HT[i].weight = HT[s1].weight + HT[s2].weight;
	 	HT[s1].parent = i;
	 	HT[s2].parent = i;
	 	HT[i].lchild = s1;
	 	HT[i].rchild = s2;
	 }
	 
	 //从叶子到根逆向求每一个字符的赫夫曼树编码 
	 stack<char> s;
	 for(i = 1; i <= n; i++)
	{
		 
		int temp = i, p, k = 0;
	 	p = HT[temp].parent;
	 	while(p)
	 	{	
	 		if(HT[p].lchild == temp)
			s.push('0');
			if(HT[p].rchild == temp)
			s.push('1');
			temp = p;
			p = HT[temp].parent;
			k++;
	 	}
	 	
	 	int j = 0;
	 	while(!s.empty())
		{
			HC[i][++j] = s.top();
			s.pop();
		}
		HC[i][0] = j;
	 }
}

void showHuffmanCode(HuffmanCode HC) 		//显示每一个字符的赫夫曼编码 
{
	char c ; 
	for(int i = 1; i <= 27; i++){
		if(i != 27)
		 {
		 	c = i + 'A' - 1;
		 	cout << c << "的赫夫曼编码是:"; 
		 }
		else
		{
			cout << "空格的赫夫曼编码是:";
		}
		for(int j = 1; j <= HC[i][0]; j++)
		{
			cout << HC[i][j];
		}
		cout << endl;
	} 
}


void TanserString(HuffmanCode HC,string s)	//将字符转化为赫夫曼编码 
{
	string ss;
	for(int i = 0; i < s.length(); i++)
	{
		if(s[i] >= 'A' && s[i] <= 'Z')
			s[i] += 32; 
		if(s[i] == ' ')
			s[i] = 'z' + 1;
	}
	
	for(int i = 0; i < s.length(); i++)
	{
		for(int j = 1; j <= HC[s[i] - 'a' + 1][0] ;j++)
		ss += HC[s[i] - 'a' + 1][j];
	}
	cout << ss << endl;
}


void TanserHuffmanCode(HuffmanCode HC,string s)	//将赫夫曼码变为字符 
{
	string ss = "", s1 = "";
	string t[27];
	for(int i = 0 ; i < 27 ;i++)
	{
		t[i] = "";
		for(int k = 1; k <= HC[i + 1][0] ;k++)
		{
			t[i] += HC[i + 1][k];
		}
	}
	for(int i = 0; i < s.size(); i++)
	{
		ss += s[i];
		for(int j = 0; j < 27; j++)
		{
			if(ss == t[j])
			{
				
				ss = "";
				if(j != 26)
				{
					s1 += j + 'a' ;
				}
				
				else if(j == 26)
				{
					s1 += ' '; 
				}
			}
		}
	}
	cout << s1 << endl;
}

void help(){
	cout << "************************************************************" << endl;
	cout << "********   1.输入HuffmanTree的参数                      ****" << endl;
	cout << "********   2.初始化HuffmanTree参数.《含有26字母及空格》 ****" << endl;
	cout << "********   3.建立HuffmanTree和编码表。                  ****" << endl; 
	cout << "********   4.输出编码表。                               ****" << endl;
	cout << "********   5.输入编码,并翻译为字符。                   ****" << endl;
	cout << "********   6.输入字符,并实现转码                       ****" << endl;
	cout << "********   7.退出                                       ****" << endl;
	cout << "************************************************************" << endl;
}



int main ()
{
	HuffmanTree HT;
	HuffmanCode HC;
	string s;
	HC = (HuffmanCode) malloc ((27+1) * sizeof(char *));
	for(int i = 1; i <= 28 ;i++)
	HC[i] = (char *)malloc((27+1) * sizeof(char));
	help();
	int a[27] = {64, 13, 22, 32, 103, 21, 15, 47, 57, 1, 5, 32, 20, 57, 63, 15, 1, 48, 51, 80, 23, 8, 18, 1, 16, 1, 168};
	
	int operator_code;
	while (1)
	{
		cout << "请输入操做 :" << endl;
		cin >> operator_code;
		if(operator_code == 1)
		{
			HuffmanCoding(HT, HC, a, 27);
			cout << "建立成功,1,2,3已完成,无需输入2,3" << endl; 
		}
		else if(operator_code == 4)
		{
			showHuffmanCode(HC); 
		}
		else if(operator_code == 5)
		{
			getchar();
			cout << "请输入HuffmanCode:";
			getline(cin,s);
			TanserHuffmanCode(HC,s);
		}
		else if(operator_code == 6)
		{
			getchar();
			cout << "请输入字符:";
			getline(cin,s);
			TanserString(HC,s);
		}
		else if( operator_code == 7)
		{
			break;
		}
		else
		{
			cout << "输入违法请从新输入" << endl; 
		}
	}
	return 0;
}