一、哈夫曼树的定义及构造思想
哈夫曼树定义:满足WPL最小的二叉树,即最优树
WPL(带权路径长度):设二叉树有n个叶结点,每个叶子结点带有权值wk,从根结点到每个叶结点长度为lk,则每个叶结点的WPL = ∑wk*lk
哈夫曼树特点:
1.没有度为1的结点,要么度为2要么度为0,因为每个结点都是由子结点两两合并而来。
2.哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树
3.n个叶结点的哈夫曼树共有2n-1个结点
简单证明:
n2——有2个子结点的结点
n1——有1个子结点的结点
n0——叶结点
n2 = n0 - 1
由于哈夫曼树没有n1,所以总结点数为n2+n0 = n - 1 + n = 2n - 1
4.对于同一组权值,存在不同的哈夫曼树
构造思想:每次把权值最小的两棵二叉树合并,合并后树的权值为合并前两棵树权值的和,所以这样子哈夫曼树的结构有点像金字塔,最顶端权值最大。那权值最小如何获得呢?我们可以想到用最小堆可以每次删除得到当前权值的最小值。
二、哈夫曼树用最小堆实现的代码
#include<iostream>
using namespace std;
#define MINDATA -100;//哨兵
//声明
typedef struct TreeNode* HuffmanTree;//哈夫曼树
struct TreeNode
{
int Weight;//权重
HuffmanTree Left,Right;//指向左右子树的指针
};
typedef struct HeapStruct* MinHeap;//最小堆
struct HeapStruct
{
HuffmanTree *arr;//存储哈夫曼的数组
int size;//当前元素个数
int capacity;
};
MinHeap Create(int maxsize);//初始化最小堆
int IsFull(MinHeap H);//判断堆是否满了
void Insert(MinHeap H,HuffmanTree item);//向堆内插入元素,生成最小堆
HuffmanTree DeleteMin(MinHeap H);//删除最小堆的元素
HuffmanTree Initialize();//初始化哈夫曼树
HuffmanTree Huffman(MinHeap H);//生成哈夫曼树
void PreOrderTraversal(HuffmanTree T);//后序遍历哈夫曼树
MinHeap BuildMinHeap(MinHeap H);//调整成最小堆
MinHeap BuildMinHeap(MinHeap H)
{
int i,parent,child;
HuffmanTree temp;
for(i=H->size/2;i>0;i--)
{ //从最后一个父结点开始,直到根结点
temp = H->arr[i];
for(parent=i; parent*2<=H->size; parent=child)
{ /*向下过滤*/
child = parent*2;
if((child != H->size) && (H->arr[child]->Weight > H->arr[child+1]->Weight))
{/*有右儿子,并且左儿子权值大于右儿子*/
child++; //child指向左右儿子中较小者
}
if(temp->Weight > H->arr[child]->Weight)
{
H->arr[parent] = H->arr[child]; //向上过滤结点-temp存放位置下移到child位置
}else{
break; //找到了合适的位置
}
}/*结束内部for循环对以H->data[i]为根的子树的调整*/
H->arr[parent] = temp; //temp(原H->data[i])存放到此处
}
return H;
}
MinHeap Create(int maxsize)
{
MinHeap H = new struct HeapStruct;
H->arr = new HuffmanTree [maxsize+1];//结构体内的数组存储的是哈夫曼树,加1是因为数组0下标,
H->size = 0;
H->capacity = maxsize;
HuffmanTree T = Initialize();
T->Weight = MINDATA;
H->arr[0] = T ;//哨兵元素
return H;
}
int IsFull(MinHeap H)
{
if(H->size==H->capacity) return 1;
else return 0;
}
void Insert(MinHeap H,HuffmanTree item)
{
int i;
if(IsNull(H))
{
cout << "最小堆已满"<<endl;
return;
}
i = ++H->size;
for(; H->arr[i/2]->Weight>item->Weight;i/=2)//其实如果最顶层元素仍比待插入元素小,那么还会继续往上顶,出现下标下益,解决方法加一个与条件:i>1,或者加一个哨兵
{
H->arr[i] = H->arr[i/2];
}
H->arr[i] = item;
}
HuffmanTree DeleteMin(MinHeap H)
{/*从最小堆H中取出权值为最小的元素,并删除一个结点*/
int parent,child;
HuffmanTree MinItem,temp = NULL;
//if( IsFull(H) )
//{
//cout <<"最小堆为空\n";
//return NULL;
// }
MinItem = H->arr[1]; //取出根结点-最小的元素-记录下来 /*用最小堆中的最后一个元素从根结点开始向上过滤下层结点*/
temp = H->arr[H->size--]; //最小堆中最后一个元素,暂时将其视为放在了根结点
for(parent=1; parent*2<=H->size; parent=child)
{
child = parent*2;
if((child != H->size) && (H->arr[child]->Weight > H->arr[child+1]->Weight))
{/*有右儿子,并且左儿子权值大于右儿子*/
child++; //child指向左右儿子中较小者
}
if(temp->Weight > H->arr[child]->Weight)
{
H->arr[parent] = H->arr[child]; //向上过滤结点-temp存放位置下移到child位置
}else{
break; //找到了合适的位置
}
}
H->arr[parent] = temp; //temp存放到此处
return MinItem;
}
HuffmanTree Initialize()//初始化哈夫曼树
{
HuffmanTree T = new struct TreeNode;
T->Weight = 0;
T->Left = T->Right = NULL;
return T;
}
HuffmanTree Huffman(MinHeap H)//构造哈夫曼树的堆,直到原数组只剩除哨兵外下一个元素,也就是将原来的元素个数合并size-1次
{//这里应该还有一个判断堆是否空的函数
int num;
HuffmanTree T;
//BuildMinHeap(H);//这个函数在这里可以注释掉,因为这里输入的值就是权值,对于原堆并非按路径权值的最小堆,需要重新按权值生成最小堆,当然这种最小堆数组存储不止权重一个元素
num = H->size;
for(int i=1;i<num;i++)//合并size-1次,这里必须要有一个num作为中间变量,否则结构不正确,
{
T = new struct TreeNode;
HuffmanTree Tmin1 = DeleteMin(H);
HuffmanTree Tmin2 = DeleteMin(H);
T->Weight = Tmin1->Weight+Tmin2->Weight;
T->Left = Tmin1;
T->Right = Tmin2;
Insert(H,T);
}
T = DeleteMin(H);//此时最小堆只剩下除哨兵外的一个元素
return T;
}
void PreOrderTraversal(HuffmanTree T)
{
if(T)
{
cout << T->Weight<<endl;
PreOrderTraversal(T->Left);
PreOrderTraversal(T->Right);
}
}
int main()
{
int item;
MinHeap H;
HuffmanTree T;
cout << "请输入您需要创建的最小堆的元素个数" <<endl;
cin >> item;
H = Create(item);
cout << "请输入各叶子对应的最小权值"<<endl;
for(int i=1;i<=7;i++)
{
T = Initialize();
cin >> T->Weight;
Insert(H,T);
}
//Huffman(H);
cout<< "先序遍历哈夫曼树"<<endl;
PreOrderTraversal(Huffman(H));
return 0;
}
结果:
三、哈夫曼编码:
跟据哈夫曼树形成的非前缀编码,一个哈夫曼树的叶结点对应一个字符,附一张图:
代码实现:在之前输入的7个叶结点上,对应7个字符
typedef struct TreeNode* HuffmanTree;
struct TreeNode
{
int Weight;
char ch;//增加了一个代表字符的变量,叶结点会被赋值相应的代表字符,非叶结点默认为NULL
HuffmanTree Left,Right;
};
void PreOrderTraversal(HuffmanTree T)
{
if(T)
{
cout << T->Weight<<endl;
PreOrderTraversal(T->Left);
PreOrderTraversal(T->Right);
}
}
void HuffmanCode(HuffmanTree BST,int depth) //哈夫曼编码
{
static int code[10]; //编码空间
if( BST )
{
if( (BST->Left == NULL) && (BST->Right == NULL))
{ //找到了叶结点
printf("字符%c对应权值为%d的哈夫曼编码为:",BST->ch,BST->Weight);
for(int i=0; i<depth; i++)
{
printf("%d",code[i]);
}
printf("\n");
}else{
code[depth] = 0; //往左子树方向编码为0,经典的树结构递归,从左至右
HuffmanCode(BST->Left,depth+1);
code[depth] = 1; //往右子树方向编码为1
HuffmanCode(BST->Right,depth+1);
}
}
}
void HuffmanDecode(char ch[],HuffmanTree BST) //ch[] 要解码的序列
{
int cnt;
int num[10];
HuffmanTree temp;
for(int i=0; i<strlen(ch); i++)
{
if(ch[i] == '0')
{
num[i] = 0;
}else{
num[i] = 1;
}
}
if( BST )
{
cnt = 0; //计数已经解码0101串的长度
while(cnt < strlen(ch))
{
temp = BST;
while((temp->Left != NULL ) && (temp->Right != NULL))
{
if(num[cnt] == 0)
{
temp = temp->Left;
}else{
temp = temp->Right;
}
cnt++;
}
printf("%c",temp->ch); //输出解码后对应结点的字符
}
}
}
结果:
code[depth] = 0;
HuffmanCode(BST->Left,depth+1);
code[depth] = 1;
HuffmanCode(BST->Right,depth+1);