标签:数据结构 频率 pre ascii 时间复杂度 思路 for 构造 数组
哈夫曼编码应该算数据结构“树”这一章最重要的一个问题了,当时大一下学期学的时候没弄懂,一年后现在算是明白了。
首先,讲讲思路。
正好这学期在学算法,这里面就用到了贪心算法,刚好练练手。
整个问题有几个关键点:
1,首先是要思考怎么样存下从txt中读取的一个所有字符中每种字符出现的次数,首先想到的应该是结构体数组,再仔细想想不对呀,时间复杂度太高了,每次判断一个字符都对知道它属于结构体数组中的哪一个,那要是txt中有很多字符,那光这个就得花好多时间。然后想想,如果读取的字符如果只有那255个ASCII中的字符时可以直接用一个整形数组来表示呀!数组的下标为整数,整数和字符数不正是通过ASCII码一一对应了吗。当然这些字符必须全部是那255个中的。所以整形数组的大小也只需255.当然了,如果对C++关联容器的知识比较熟悉,用关联容器更方便,毕竟,我们这种方法其实就是在模拟关联容器。
2,然后我们怎么从刚才得到的各个字符的频率来整出一颗哈夫曼树呢?首先,离散数学上讲的构建哈夫曼树应该是比较容易理解的,就是每次选取两个权值(也就是这里的频率)最小的两个节点作为左右孩子(小的在左,大的在右),然后把它们的权值之和作为一个新的待选择的结点去和剩余结点判断,自底向上构建,直到剩余权值最大的一个结点,完毕。虽然这样说着很简单,但是落实到代码就值得思考了。我们该怎么样表示这样一棵树呢?习惯性地用指针?认真思考后我们发现,用指针行不通,因为这棵树是自底向上构建的,我们在构建完成之前是不知道这棵树的遍历序列的,也就没法通过递归的形式来创建这棵树。那么,我们现在应该想到的是用数组,没错,用数组可以!显然是结构体数组,所以得考虑结构体中要有哪些变量,首先要有每个结点表示的字符,对应的权值,还要有左右孩子的下标,双亲的下标。这样就可以在数组中存下这棵树了。
3,在得到哈夫曼树后,该怎么输出每个字符对应的哈夫曼编码呢?从每个叶子结点出发,逐步向根结点方向遍历,每次到达其双亲时,判断自己是双亲的左孩子还是右孩子,如果是左孩子,在该叶子表示的字符对应的编码(用string表示)加上0,否则加上1。然后再找到双亲的双亲,。。。直到到达根结点(根结点没有双亲,对应的双亲下标可以设为0),由于这样得到的编码01字符串是反的,所以我们要反一下就得到了正确的编码。
这里放一个ppt,是我们老师上课讲的,便于理解 http://files.cnblogs.com/files/journal-of-xjx/huffman.pptx
注意我是把一个txt文件中的所有字符读到一个string中去的。自己测试的时候随便放一个input.txt进去就可以了(注意必须是那255个)
代码如下:
1 #include <iostream> 2 #include <fstream> 3 #include <algorithm> 4 #include <string.h> 5 #include <stdlib.h> 6 7 using namespace std; 8 9 #define NUM_CHARS 256 //读取的字符串中最多出现这么多种字符 10 #define MAX_FREQ 256 //最大频率必须小于256 11 #define MAX_SIZE 512 12 //Huffman Tree结点 13 typedef struct HuffNode 14 { 15 char data; 16 unsigned int freq; 17 int parent; 18 int lchild; 19 int rchild; 20 }HuffNode; 21 //编码结点 22 typedef struct HuffCode 23 { 24 char data;//保存字符 25 string s;//保存字符对应的编码 26 }HuffCode; 27 28 //给定一个字符串,把字符的出现频率保存到freqs数组中 29 //(没有出现在字符串中的字符我们不在freqs数组中保存它们为零) 30 int Create_freq_array(unsigned int (&freqs)[NUM_CHARS],string s, int &char_size)//传入数组的引用 31 { 32 int i, maxfreq = 0; 33 for(int i=0;i<NUM_CHARS;i++) 34 freqs[i] = 0;//注意传入的数组的各元素先赋值为0 35 for(auto iter =s.begin(); iter!=s.end(); iter++) 36 { 37 freqs[*iter]++;//*iter为char型,这里转换成了int型,即以某个字符的ASCII码作为它在freq数组中的下标,注意这种方式不能表示非ASCII码字符! 38 if(freqs[*iter] > maxfreq) 39 maxfreq = freqs[*iter];//每次记得更新maxfreq的值 40 } 41 //把字符频率压缩到一个字节。 scaled freqs to (0~255) 42 if(maxfreq > 0xff)//最大出现频率大于255 43 { 44 for(i=0; i<NUM_CHARS; i++) 45 { 46 if(freqs[i]) 47 { 48 freqs[i] = (int)(freqs[i] * 255.0 / maxfreq + 0.5);//最大频率为255 49 if(freqs[i] == 0)//要确保不会被缩小成0! 50 freqs[i] = 1; 51 } 52 } 53 } 54 55 for(i=0; i<NUM_CHARS; i++)//计算char_size值 56 { 57 if(freqs[i]) 58 { 59 char_size++; 60 } 61 } 62 return 0; 63 } 64 65 //打印字符频率表 66 int Print_freqs(unsigned int (&freqs)[NUM_CHARS],int n) 67 { 68 int i; 69 char c; 70 for(i = 0; i < NUM_CHARS; i++) 71 { 72 if(freqs[i]) 73 { 74 c = i;//把i以ASCII码值还原出对应的字符 75 cout << "字符" << c << "出现的频率为:" << freqs[i] << endl; 76 } 77 78 } 79 cout << "(注:以上频率均为相对值,并不代表实际字符出现的次数)" <<endl; 80 cout << endl << "以上共出现" << n << "种字符" << endl <<endl; 81 return 0; 82 } 83 84 int Build_Huffman_tree(unsigned int (&freqs)[NUM_CHARS],HuffNode (&Huffman_array)[MAX_SIZE],int n)//n表示freqs数组中实际包含的字符种类数 85 { 86 char c; 87 int k = 0,x1,x2; 88 unsigned int m1, m2; 89 90 for(int i=0;i<NUM_CHARS;i++)//把前n个叶结点的信息输入Huffman_array数组 91 { 92 if(freqs[i]) 93 { 94 c=i;//还原字符 95 Huffman_array[k].data = c; 96 Huffman_array[k].freq = freqs[i]; 97 Huffman_array[k].parent = 0; 98 Huffman_array[k].lchild = 0; 99 Huffman_array[k].rchild = 0; 100 k++; 101 } 102 } 103 for(int i=n;i<2*n-1;i++)//处理剩下n-1个非叶子结点 104 { 105 Huffman_array[k].data = ‘#‘; 106 Huffman_array[k].freq = 0; 107 Huffman_array[k].parent = 0; 108 Huffman_array[k].lchild = 0; 109 Huffman_array[k].rchild = 0; 110 } 111 // 循环构造 Huffman 树 112 for(int i=0; i<n-1; i++) 113 { 114 m1=m2=MAX_FREQ; // m1、m2中存放两个无父结点且结点权值最小的两个结点 115 x1=x2=0; //x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号 116 /* 找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树 */ 117 for (int j=0; j<n+i; j++) 118 { 119 if (Huffman_array[j].freq < m1 && Huffman_array[j].parent==0) 120 { 121 m2=m1; 122 x2=x1; 123 m1=Huffman_array[j].freq ; 124 x1=j; 125 } 126 else if (Huffman_array[j].freq < m2 &&Huffman_array[j].parent==0) 127 { 128 m2=Huffman_array[j].freq ; 129 x2=j; 130 } 131 } /* end for */ 132 /* 设置找到的两个子结点 x1、x2 的父结点信息 */ 133 Huffman_array[x1].parent = n+i; 134 Huffman_array[x2].parent = n+i; 135 Huffman_array[n+i].freq = Huffman_array[x1].freq + Huffman_array[x2].freq ; 136 Huffman_array[n+i].lchild = x1; 137 Huffman_array[n+i].rchild = x2; 138 } 139 return 0; 140 } 141 //哈夫曼编码,输出string中各种字符对应的编码 142 int Huffman_code(HuffNode(&Huffman_array)[MAX_SIZE],HuffCode (&Huffman_code_array)[NUM_CHARS],int n) 143 { 144 int temp; 145 for(int i = 0;i < n;i++) 146 { 147 temp = i;//当前处理的Huffman_array数组下标 148 Huffman_code_array[i].data = Huffman_array[i].data; 149 while(Huffman_array[temp].parent) 150 { 151 if(Huffman_array[Huffman_array[temp].parent].lchild == temp)//左孩子为0 152 { 153 Huffman_code_array[i].s += ‘0‘; 154 } 155 else//右孩子为1 156 { 157 Huffman_code_array[i].s += ‘1‘; 158 } 159 temp = Huffman_array[temp].parent; 160 } 161 reverse(Huffman_code_array[i].s.begin(), Huffman_code_array[i].s.end());//注意翻转每一个string,这里用到了#include <algorithm> 162 } 163 return 0; 164 } 165 166 int Print_huffman_code(HuffCode (&Huffman_code_array)[NUM_CHARS],int n) 167 { 168 for(int i = 0;i < n;i++) 169 { 170 cout << "字符" << Huffman_code_array[i].data << "对应的哈夫曼编码为:" << Huffman_code_array[i].s << endl; 171 } 172 cout << endl; 173 return 0; 174 } 175 176 int main() 177 { 178 ifstream in("input.txt",ios::in);//从input.txt中读取输入数据 179 string s,temp; 180 int char_size = 0;//用以保存string中所包含的字符种类 181 unsigned int freqs[NUM_CHARS]; 182 HuffNode Huffman_array[MAX_SIZE]; 183 HuffCode Huffman_code_array[NUM_CHARS]; 184 while(getline(in,temp))//按行读取一个txt文件中的各个字符到一个string,注意这种读取方式只有字符之间的空格才会读入,其他空格不读取 185 { 186 s += temp; 187 } 188 cout << "输入的字符总数为: " << s.size() << endl << endl << "其中:" << endl;//string中包含的字符数 189 Create_freq_array(freqs,s,char_size); 190 Print_freqs(freqs,char_size); 191 Build_Huffman_tree(freqs,Huffman_array,char_size); 192 Huffman_code(Huffman_array,Huffman_code_array,char_size); 193 Print_huffman_code(Huffman_code_array,char_size); 194 return 0; 195 }
参考了别人的一些代码。
标签:数据结构 频率 pre ascii 时间复杂度 思路 for 构造 数组
原文地址:http://www.cnblogs.com/journal-of-xjx/p/6670464.html