数据结构与算法--哈夫曼树及其应用

一、哈夫曼树的基本概念

          1) 路径: 从树中一个结点到另一个结点之间的分支构成这两个结点间的路径

          2) 结点的路径长度: 两结点间路径上的分支数

          3) 树的路径长度:从树根到每一个结点的路径长度之和;记作TL

          4) 结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树

          5) 权: 将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权

          6) 结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积

          7) 树的带权路径长度(WPL): 树中所有叶子结点的带权路径长度之和

          8) 哈夫曼树: 带权路径长度最短的树

               //"带权路径长度最短"是在"度相同"的树中比较而得的结果,因此有最优二叉树、最优三叉树之称等等

          9) 哈夫曼树-最优二叉树: 带权路径长度最短的二叉树

数据结构与算法--哈夫曼树及其应用_第1张图片

二、哈夫曼树的构造算法

          贪心算法: 构造哈夫曼树时首先选择权值小的叶子结点

      1.哈夫曼树算法(构造哈夫曼树的方法)

      1) 根据n个给定的权值{W1,W2,...,Wn}构成n棵二叉树的森林F={T1,T2,...,Tn},其中Ti只有一个带权为Wi的根结点

            //构造森林全是根

      2) 在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,

          且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和

            //选用两小造新树

      3) 在F中删除这两棵树,同时将新得到的二叉树加入森林中

            //删除两小添新人

      4) 重复2)和3),直到森林中只有一棵树为止,这棵树即为哈夫曼树

            //重复23剩单根

数据结构与算法--哈夫曼树及其应用_第2张图片

数据结构与算法--哈夫曼树及其应用_第3张图片

三、哈夫曼树构造算法实现

      1.采用顺序存储结构(链式也行)-----一维结构数组

typedef struct                  //结点类型定义
{
	int weight;            //权值
	int parent, lch, rch;              //双亲结点、左孩子及右孩子下标
}HTNode,*HuffmanTree;
哈夫曼树中共有2n-1个结点,不使用0下标,数组大小为2n
哈夫曼树中的结点下标i weight parent lch rch
1        
2        
3        
4        
...        
...        
2n-1        

      2.初始化数组并建立哈夫曼树

void Select(HuffmanTree HT,int n,int *s1,int *s2)
{
	*s1 = *s2 = 0;
	int min1 = INT_MAX;                       //最小值,INT_MAX在中定义
	int min2 = INT_MAX;                       //次小值
	for (int i = 1; i <= n; i++)
	{
		if (HT[i].parent == 0)                    //筛选没有双亲的最小和次小值下标
		{
			if (HT[i].weight < min1)                //如果比最小值小
			{
				min2 = min1;
				*s2 = *s1;
				min1 = HT[i].weight;
				*s1 = i;
			}
			else if((HT[i].weight >= min1) && (HT[i].weight < min2)) 
                                                  //如果大于等于最小值且小于次小值
			{
				min2 = HT[i].weight;
				*s2 = i;
			}
			else                                           //如果大于次小值则什么也不做
			{
				;
			}
		}
	}
}

void CreatHuffmanTree(HuffmanTree HT, int n)        //初始化数组并建立哈夫曼树
{
	if (n <= 1)
		return;
	int m = 2 * n - 1;           //数组共2n-1个元素
	HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));      //0号单元不用,HT[m]表示根结点
	for (int i = 1; i <= m; i++)              //将2n-1个元素的parent,lch,rch设置为0
	{
		HT[i].parent = 0;
		HT[i].lch = 0;
		HT[i].rch = 0;
	}
	for (int i = 1; i <= n; i++)                //输入前n个元素的weight值
	{
		scanf("%d", &HT[i].weight);
	}
        for (int i = n + 1; i <= m; i++)              //合并产生n-1个结点----构造Huffman树
	{       
		Select(HT, i - 1, &s1, &s2);      //在HT[k](1<=k<=i-1)中选择两个其双亲为0,
                                          //且权值最小的结点下标存在s1,s2中
		HT[s1].parent = i;                         //从F中删除s1
		HT[s2].parent = i;                         //从F中删除s1
		HT[i].lch = s1;
		HT[i].rch = s2;                             //s1,s2分别作为i的左右孩子
		HT[i].weight = HT[s1].weight + HT[s2].weight;         //i的权值为左右孩子权值之和
	}
}

四、哈夫曼编码思想(哈夫曼编码是最优前缀码)

        1) 统计字符集中每个字符在电文中出现的平均频率(概率越大,要求编码越短)

        2) 利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树,则概率越大的结点,路径越短

        3) 在哈夫曼树的每个分支上标上0或1:

                       结点的左分支标0右分支标1

                       把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码

数据结构与算法--哈夫曼树及其应用_第4张图片

      1.为什么哈夫曼编码能够保证是前缀编码?

           因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其它叶结点编码的前缀

      2.为什么哈夫曼树编码能够保证字符编码总长最短?

           因为哈夫曼树的带权路径长度最短,故字符编码的总长最短

五、哈夫曼编码的算法实现

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode *HC, int n) 
                                        //从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
{
	*HC = (char**)malloc((n + 1)*sizeof(char *));     //分配n个字符编码的头指针
	char *cd = (char *)malloc(n * sizeof(char));      //分配临时存放编码的动态数组空间
	cd[n - 1] = '\0';                                 //编码结束符
	for (int i = 1; i <= n; i++)                //逐个字符求哈夫曼编码
	{
		int start = n - 1;
		int c = i;
		int f = HT[i].parent;
		while (f != 0)                        //从叶子结点开始向上回溯,直到根结点
		{
			--start;                         //回溯一次start向前指一个位置
			if (HT[f].lch == c)            //结点c是f的左孩子,则生成代码0
				cd[start] = '0'; 
			else                               //结点c是f的右孩子,则生成代码1
				cd[start] = '1';
			c = f;
			f = HT[f].parent;                    //继续向上回溯
		}                                         //求出第i个字符的编码
		(*HC)[i] = (char*)malloc((n - start) * sizeof(char));  //为第一个字符串编码分配空间
		strcpy((*HC)[i], &cd[start]);          //将求得的编码从临时空间cd复制到HC的当前行中
	}
	free(cd);                      //释放临时空间
}

六、文件的编码和解码

        1.编码

          1) 输入各字符及其权值

          2) 构造哈夫曼树----HT[i]

          3) 进行哈夫曼编码---HC[i]

          4) 查HC[i],得到各字符的哈夫曼编码

        2.解码

          1) 构造哈夫曼树

          2) 依次读入二进制码

          3) 读入0,则走向左孩子;读入1,则走向右孩子

          4) 一旦到达某叶子时,即可译出字符

          5) 然后再从根出发继续译码,直到结束

你可能感兴趣的:(数据结构与算法)