哈夫曼树的创建以及哈夫曼编码的实现
哈夫曼树的创建
- 初始化HT[1……2n-1]:lch=rch=parent=0;
- 输出初始n个叶子结点:置HT[1……n]的weight值;
- 进行以下n-1次合并,依次产生n-1个结点HT[i],i=n+1……2n-1:
- 在HT[1……i-1]中选两个未被选过(从parent==0的结点中选)的weight最小的两个结点HT[s1]和HT[s2],s1和s2为两个最小结点下标;
- 修改HT[s1]和HT[s2]的parent值:HT[s1].parent=i;HT[s2].parent=i;
- 修改新产生的HT[i]:
- HT[i].weight=HT[s1].weight+HT[s2].weight;
- HT[i].lch=s1;HT[i].rch=s2;
哈夫曼编码
不等长编码设计关键:必须使任一字符的编码都不是另一个字符的编码的前缀。——前缀编码
哈夫曼编码:前缀码+电文总长度最短
方法:
- 统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)
- 利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。
- 在哈夫曼树的每个分支上标0/1;结点的左分支标0,右分支标1;把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符编码。
#include <iostream>
#include<cmath>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100
typedef int Status;
typedef char** HuffmanCode;
using namespace std;
//哈夫曼树的结点的类型
typedef struct {
int weight;//权重
char word;//结点存放的字符
int parent, lch, rch;
}HTNode, * HuffmanTree;
//构造森林全是根
int InitHuffmanTree(HuffmanTree& T) {
int n;
cout << "请输入哈夫曼树的结点的个数:" << "";
cin >> n;
cout << "请按顺序输入每个结点的权值和字符:" << "\n";
for (int i = 1; i <= n; i++) {
cin >> T[i].weight;
cin>>T[i].word;
T[i].parent = 0;
T[i].lch = 0;
T[i].rch = 0;
}
return n;
}
//选用两小造新树
void createHuffmanTree(HuffmanTree& H, int n) {
for (int i = n + 1; i <= 2 * n - 1; i++)
{
int minIndex = 0, secondMinIndex = 0;
int min = 1000000, secondMin = 1000000;;//最小值现在设置为无穷大
//选择parent为0的最小的两个结点构造一棵新的树,然后把原来这两棵树删除掉(即把原来的两棵树的parent设置为这个新的节点)
for (int j = 1; j < i; j++) {
if (H[j].parent == 0)
{
if (H[j].weight <= min) {
secondMin = min;
min = H[j].weight;
secondMinIndex = minIndex;
minIndex = j;
}
if (H[j].weight > min && H[j].weight < secondMin) {
secondMin = H[j].weight;
secondMinIndex = j;
}
}
}
//已经选出来了最小权重对应的结点的下标为minIndex,第二小的结点对应的下标为secondMinIndex
H[i].parent = 0;
H[i].lch = minIndex;
H[i].rch = secondMinIndex;
H[i].weight = H[minIndex].weight + H[secondMinIndex].weight;
H[i].word = NULL;
//哎啊,忘记把lch和rch的parent设置为新节点啦
H[minIndex].parent = i;
H[secondMinIndex].parent = i;
}
}
void Transerver(HuffmanTree H, int n) {
for (int i = 1; i < 2 * n; i++) {
if (i <= n) {
printf("%c %d %d %d %d\n",H[i].word,H[i].weight, H[i].parent, H[i].lch, H[i].rch);
}
else {
printf("%d %d %d %d\n", H[i].weight, H[i].parent, H[i].lch, H[i].rch);
}
}
}
//哈夫曼编码
void createHuffmanCode(HuffmanTree H, HuffmanCode& HC, int n) {
char* cd = new char[n];//用于记录每一个字符的编码
//这里说一下为要n呢?n个结点构成的哈夫曼树,
//每两个结点之间就新建一个连接,最多有n-1个分支
//这里定义字符数组的长度为n是因为还要存储字符串的结束符\0
cd[n - 1] = '\0';
for (int i = 1; i <= n; i++)
{
//因为是从叶子结点开始找到根结点,所以在写入字符数组的时候的顺序是反着来写的
int start = n - 1;
//为了找到究竟是parent的lch还是rch,我们这里还需要存储当前结点
int pre = i;
int j = H[i].parent;
//从叶子结点开始找,一直找到根(什么时候是根呢,就是那个parent是0的那个啊)
while (j != 0) {
--start;
if (H[j].lch==pre)//上一个结点是parent的左孩子
{
//左孩子给编号0
cd[start] = '0';
}
else {
//右孩子给编号1
cd[start] = '1';
}
pre = j;
j = H[j].parent;
}
HC[i] = new char[n];
strcpy(HC[i], &cd[start]);
}
delete cd;
}
//用一维数组存储哈夫曼树
int main() {
HuffmanTree H = new HTNode[MAXSIZE];
int n = InitHuffmanTree(H);
createHuffmanTree(H, n);
Transerver(H, n);
HuffmanCode HC = new char*[MAXSIZE];
//要注意了,虽然讲课的时候讲的是new char*[n+1],但是定义数组的时候,数组的大小必须确定,虽然也可以写,但是呢不太好
createHuffmanCode(H, HC, n);
for (int i = 1; i <= n; i++)
{
printf("%c %s\n", H[i].word, HC[i]);
}
return 0;
}