最优二叉树(赫夫曼树)

赫夫曼树的介绍(写的不好地方大佬请指教)

最优二叉树又称哈夫曼树,是带权路径最短的二叉树。根据节点的个数,权值的不同,最优二叉树的形状也不同。

图 6-34 是 3 棵最优二叉树的例子,它们共同的特点是带权节点都是叶子节点,权值越小,就离根节点也远,那么我们是如何构建这颗最优二叉树

步骤如下:

那如何创建这一个哈夫曼树呢?(百度百科)

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

 如:对 下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下:

当建立好哈夫曼树,我们要将其进行编码,要将权值表示改为0,1表示,叶子节点的左边改为0,右边改为1

这样子比较方便在网络上传输,因为哈夫曼树的研究目的就是为了解决早期远距离通信(电报)的数据传输的优化问题。

 

接下来我们来分析一下,这样做对数据的优化体现在哪里?

例如,如上图。6-12-9,

假如们要传输一端报文:BADCADFEED,那么我们可以用相应的二进制表示

字母 a b c d e f
二进制字符 000 001 010 011 100 101

 这样真正的传输的数据编码二进制后就是:001000011010000011101100100011(共30个字符),可见如果传输数据过大,这个报文的编码也就越大了。

但是如果我们按照上面的哈夫曼树进行编码的话

字母 a b c d e f
二进制字符 01 1001 101 00 11 1000

新编码后的数据是:1001010010101001000111100(共25个字符)

大约节约了17%的存储或传输文本,随着字符的增加和多字符权重的不同,这种压缩会更加突显出来优势。

0和1是比较容易混淆的,为了设计出来长度不相等的编码,我们就必须有一种规定,就是任一字符的编码都不是另一个字符的编码的前缀,这种编码被称为前缀编码。

我们可以发现通过哈夫曼编码形成的每个节点的编码例如:1000,1000混淆的10,100的类似的编码了。

当接收者收到了这个已经经过编码的二进制数后,我们要如何进行解码,才能看到发送者真正想发给接收者的消息呢?

解码的时候,必须用到双方约定好的哈夫曼树:

从根节点开始遍历,就可以知道A是01,E是11,B是 1001 ,其余的节点信息也可以相应的得到,从而成功解码。

 哈夫曼树的节点存储结构

1 //哈夫曼树结构
2 ; typedef struct{
3     unsigned int weight;          //权重
4     unsigned int parent, lchild, rchild;  //树的双亲节点,和左右孩子
5 
6 }HTNode, *HuffmanTree;
7 
8 typedef char**  HuffmanCode;

基本思路就是:

1、首先我们要建立一个哈夫曼树。

2、这个哈夫曼树有的特点在上面有介绍。

3、对哈夫曼树进行0,1编码

4,最后打印出已经编码完成的哈夫曼树。

里面最难的两部步应该是建树,解码输出(可能我比较笨把,搞了二天才搞明白)

1、建树,根据节点的权重建立,每次从的序列中比较出两个最小的权重,建立出一颗树,然后再从剩余的节点中继续抽取节点权重最小的节点,继续建树,这边我采用的是迭代方式;

2、解码输出,采用的是叶子节点逆序遍历到根节点,将0,1存储到字符数组里面,然后再将数组输出。

里面是具体实现的代码,里面也有注释

1 //函数声明
2 int Min(HuffmanTree T, int i); //求i个节点中的最小权重的序列号,并返回
3 void Select(HuffmanTree T, int i, int& s1, int& s2); //从两个最小权重中选取最小的(左边)给s1,右边的给s2
4 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n);//哈夫曼编码与解码

函数的具体实现:

 1 //返回i个节点中权值最小的树的根节点的序号,供select()调用
 2 int Min(HuffmanTree T, int i){
 3     int j, flag;
 4     unsigned int k = UINT_MAX;  //%d-->UINT_MAX = -1,%u--->非常大的数
 5     for (j = 1; j <= i; j++)
 6         if (T[j].weight < k && T[j].parent == 0)
 7             k = T[j].weight, flag = j;                  //
 8     T[flag].parent = 1;    //将parent标志为1避免二次查找
 9 
10     return flag;   //返回
11 }
12 
13 void Select(HuffmanTree T, int i,int& s1,int& s2){
14     //在i个节点中选取2个权值最小的树的根节点序号,s1为序号较小的那个
15     int j;
16     s1 = Min(T,i);
17     s2 = Min(T,i);
18     if (s1 > s2){
19         j = s1;
20         s1 = s2;
21         s2 = j;
22     }
23 }
24 
25 //HuffmanCode代表的树解码二进制值
26 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n){
27     //w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC
28     int m, i, s1, s2, start;
29     unsigned c, f;
30     char* cd;   
31     //分配存储空间
32     HuffmanTree p;
33     if (n <=1)
34         return;
35     //n个字符(叶子节点)有2n-1个树节点,所以树节点m
36     m = 2 * n - 1;
37     HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode));  //0号元素未用
38     //这一步是给哈夫曼树的叶子节点初始化
39     for (p = HT + 1, i = 1; i <= n; ++i, ++p,++w)
40     {
41         (*p).weight = *w;
42         (*p).lchild = 0;
43         (*p).rchild = 0;
44         (*p).parent = 0;
45     }
46     //这一步是给哈夫曼树的非叶子节点初始化
47     for (; i <= m; ++i, ++p){
48         (*p).parent = 0;
49     }
50     /************************************************************************/
51     /* 做完准备工作后 ,开始建立哈夫曼树                                                               
52     /************************************************************************/
53     for (i = n + 1; i <= m; i++)
54     {
55         //在HT[1~i-1]中选择parent=0且weigh最小的节点,其序号分别为s1,s2
56         Select(HT, i - 1, s1, s2);  //传引用
57         HT[s1].parent = HT[s2].parent= i;
58         HT[i].lchild = s1;
59         HT[i].rchild = s2;
60         HT[i].weight = HT[s1].weight + HT[s2].weight;
61     }
62     /************************************************************************/
63     /* 从叶子到根逆求每个叶子节点的哈夫曼编码                                          */
64     /************************************************************************/
65     //分配n个字符编码的头指针向量,([0]不用)
66     HC = (HuffmanCode)malloc((n + 1)*sizeof(char*));  
67     cd = (char*)malloc(n*sizeof(char));   //分配求编码的工作空间
68     cd[n - 1] = '\0'; //结束符
69     for (i = 1; i <= n; i++)  //每个节点的遍历
70     {
71         start = n - 1;
72         for (c = i, f = HT[i].parent; f != 0; c = f,f = HT[f].parent){ //每个节点到根节点的遍历
73             //从叶子节点到根节点的逆序编码
74             if (HT[f].lchild == c)
75                 cd[--start] = '0';
76             else
77                 cd[--start] = '1';
78         }
79         HC[i] = (char*)malloc((n - start)*sizeof(char));  //生成一个块内存存储字符
80         //为第i个字符编码分配空间
81         strcpy(HC[i], &cd[start]);  //从cd赋值字符串到cd
82     }
83     free(cd);  //释放资源
84 }

 这里面有一个地方钻牛角尖了,卡了一段时间,那就是我们输入的权重是存储在w这块内存里面的,并通过权重建立起来的树,于是HT内存存储的第一个节点也就是我们输入权重所对应的第一个节点。于是我们开始逆序输出解码时,所对应的每个节点的解码与权重所对应的一一对应。

主函数具体实现:

 1 int main(){
 2 
 3     HuffmanTree HT;
 4     HuffmanCode HC;
 5     
 6     int *w, n, i;
 7     printf("请输入权值的个数(>1):");
 8     scanf_s("%d",&n);
 9 
10     w = (int*)malloc(n*sizeof(int));
11     printf("请依次输入%d个权值(整形):\n",n);
12 
13     for (i = 0; i <= n - 1;i++)
14     {
15         scanf_s("%d",w+i);
16     }
17      HuffmanCoding(HT, HC, w, n);
18 
19     for (i = 1; i <= n;i++)
20     {
21         puts(HC[i]);
22     }
23     return 0;
24 }

全部代码实现

  1 #include<string.h>
  2 #include<malloc.h>  //malloc()等
  3 #include<stdio.h>
  4 #include<stdlib.h>
  5 #include<ctype.h>
  6 #include<limits.h>
  7 #include<iostream>
  8 
  9 #define TRUE 1
 10 #define FALSE 1
 11 #define OK 1
 12 #define ERROR 1
 13 #define INFEASIBLE -1
 14 
 15 typedef int Status;
 16 typedef int Boolean;
 17 /************************************************************************/
 18 /* 最优二叉树简称:哈夫曼树                                                                     */
 19 /************************************************************************/
 20 //哈夫曼树结构
 21 ; typedef struct{
 22     unsigned int weight;          //权重
 23     unsigned int parent, lchild, rchild;  //树的双亲节点,和左右孩子
 24 
 25 }HTNode, *HuffmanTree;
 26 
 27 typedef char**  HuffmanCode;
 28 
 29 
 30 //返回i个节点中权值最小的树的根节点的序号,供select()调用
 31 int Min(HuffmanTree T, int i){
 32     int j, flag;
 33     unsigned int k = UINT_MAX;  //%d-->UINT_MAX = -1,%u--->非常大的数
 34     for (j = 1; j <= i; j++)
 35         if (T[j].weight < k && T[j].parent == 0)
 36             k = T[j].weight, flag = j;                  //
 37     T[flag].parent = 1;    //将parent标志为1避免二次查找
 38 
 39     return flag;   //返回
 40 }
 41 
 42 void Select(HuffmanTree T, int i,int& s1,int& s2){
 43     //在i个节点中选取2个权值最小的树的根节点序号,s1为序号较小的那个
 44     int j;
 45     s1 = Min(T,i);
 46     s2 = Min(T,i);
 47     if (s1 > s2){
 48         j = s1;
 49         s1 = s2;
 50         s2 = j;
 51     }
 52 }
 53 
 54 //HuffmanCode代表的树解码二进制值
 55 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n){
 56     //w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC
 57     int m, i, s1, s2, start;
 58     unsigned c, f;
 59     char* cd;   
 60     //分配存储空间
 61     HuffmanTree p;
 62     if (n <=1)
 63         return;
 64     //n个字符(叶子节点)有2n-1个树节点,所以树节点m
 65     m = 2 * n - 1;
 66     HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode));  //0号元素未用
 67     //这一步是给哈夫曼树的叶子节点初始化
 68     for (p = HT + 1, i = 1; i <= n; ++i, ++p,++w)
 69     {
 70         (*p).weight = *w;
 71         (*p).lchild = 0;
 72         (*p).rchild = 0;
 73         (*p).parent = 0;
 74     }
 75     //这一步是给哈夫曼树的非叶子节点初始化
 76     for (; i <= m; ++i, ++p){
 77         (*p).parent = 0;
 78     }
 79     /************************************************************************/
 80     /* 做完准备工作后 ,开始建立哈夫曼树                                                               
 81     /************************************************************************/
 82     for (i = n + 1; i <= m; i++)
 83     {
 84         //在HT[1~i-1]中选择parent=0且weigh最小的节点,其序号分别为s1,s2
 85         Select(HT, i - 1, s1, s2);  //传引用
 86         HT[s1].parent = HT[s2].parent= i;
 87         HT[i].lchild = s1;
 88         HT[i].rchild = s2;
 89         HT[i].weight = HT[s1].weight + HT[s2].weight;
 90     }
 91     /************************************************************************/
 92     /* 从叶子到根逆求每个叶子节点的哈夫曼编码                                          */
 93     /************************************************************************/
 94     //分配n个字符编码的头指针向量,([0]不用)
 95     HC = (HuffmanCode)malloc((n + 1)*sizeof(char*));  
 96     cd = (char*)malloc(n*sizeof(char));   //分配求编码的工作空间
 97     cd[n - 1] = '\0'; //结束符
 98     for (i = 1; i <= n; i++)  //每个节点的遍历
 99     {
100         start = n - 1;
101         for (c = i, f = HT[i].parent; f != 0; c = f,f = HT[f].parent){ //每个节点到根节点的遍历
102             //从叶子节点到根节点的逆序编码
103             if (HT[f].lchild == c)
104                 cd[--start] = '0';
105             else
106                 cd[--start] = '1';
107         }
108         HC[i] = (char*)malloc((n - start)*sizeof(char));  //生成一个块内存存储字符
109         //为第i个字符编码分配空间
110         strcpy(HC[i], &cd[start]);  //从cd赋值字符串到cd
111     }
112     free(cd);  //释放资源
113 }
114 
115 //函数声明
116 int Min(HuffmanTree T, int i); //求i个节点中的最小权重的序列号,并返回
117 void Select(HuffmanTree T, int i, int& s1, int& s2); //从两个最小权重中选取最小的(左边)给s1,右边的给s2
118 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n);//哈夫曼编码与解码
119 
120 int main(){
121 
122     HuffmanTree HT;
123     HuffmanCode HC;
124     
125     int *w, n, i;
126     printf("请输入权值的个数(>1):");
127     scanf_s("%d",&n);
128 
129     w = (int*)malloc(n*sizeof(int));
130     printf("请依次输入%d个权值(整形):\n",n);
131 
132     for (i = 0; i <= n - 1;i++)
133     {
134         scanf_s("%d",w+i);
135     }
136      HuffmanCoding(HT, HC, w, n);
137 
138     for (i = 1; i <= n;i++)
139     {
140         puts(HC[i]);
141     }
142     return 0;
143 }
View Code

 参考资料:

《大话数据结构》

《数据结构》算法实现与解析  高一凡著

《数据结构》严奶奶 

猜你喜欢

转载自www.cnblogs.com/liuzeyu12a/p/10475980.html