带权路径长度WPL:从根结点到各个叶子结点的路径长度和相应叶子结点权值的乘积之和
哈夫曼树:带权路径长度最小的二叉树(最优二叉树)
-没有度为1的结点,有n个叶子结点的哈夫曼树共有2n-1个结点
-任意非叶结点的左右子树交换后依旧是哈夫曼树
-对同一组权值,存在多个不同的哈夫曼树,但带权路径长度都相同
哈夫曼树的构造:将权值按升序排列,每次把权值最小的两棵二叉树合并为一棵树,合并产生的新结点的权值为左右子树的权值之和,最终的根结点的权值即为最短带权路径长度。
如何找到权值最小的结点?——最小堆
堆:有优先权的队列,依照优先权(关键字)大小而不是入队顺序进行出队。即,插入时都是在队头插入,但删除时要查找最小的关键字;或者在插入的时候进行排序,删除时直接删去队尾元素。
存储结构:用数组表示的有序的完全二叉树,任一结点的关键字是其子树中所有结点的最小值.
最小堆的数据结构定义
typedef struct Heap *MinHeap;
struct Heap {
HuffmanTree *Data; //存储哈夫曼树的结点的数组
int Size; //堆当前存储的元素个数
int Capacity; //堆的最大容量
};
最小堆的基本操作函数定义
-生成一个空的最小堆
MinHeap CreateMinHeap(int MaxSize) //创建一个空的最小堆
{
MinHeap H = (MinHeap)malloc(sizeof(struct Heap));
H->Data = (HuffmanTree)malloc((MaxSize+1)*sizeof(struct TNode));
H->Size = 0;
H->Capacity = MaxSize;
H->Data[0]->Weight = MINDATA; //哨兵,比所有结点都要小的值#define MINDATA -1
return H;
}
-创建最小堆
void PercDown(MinHeap H, int p) //调整算法
{
int Parent, Child;
HuffmanTree MinT,temp;
MinT = H->Data[p]; //取出根结点
temp = H->Data[H->Size--]; //最小堆中的最后一个元素
for(Parent = p; Parent*2 <= H->Size; Parent = Child) { //在有左孩子的条件下,从根结点开始向下比较
Child = Parent*2; //先让Child指向左孩子
if((Child != H->Size) && (H->Data[Child] > H->Data[Child+1])) //如果有右孩子且右孩子比左孩子小
Child++; //Child指向右孩子
if(temp <= H->Data[Child]) //如果最后这个元素比根结点的左右孩子都小,即temp是最小元素
break;
else //否则将左右孩子中小的那一个移动到根结点
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = temp;
}
void BuildMinHeap(MinHeap H) //先形成一个完全二叉树,再不断调整以满足哈夫曼树的特性
{
int i;
for(i = H->Size/2; i > 0; i--)
PercDown(H,i);
}
-最小堆的插入
bool IsFull(MinHeap H) //判断最小堆是否已满
{
if (H->Size == H->Capacity)
return true;
else
return false;
}
void Insert(MinHeap H, HuffmanTree T) //将元素T插入最小堆,元素类型为树结点
{
int i;
if (IsFull(H)) {
printf("最小堆已满");
return;
}
i = ++H->Size; //i插入到堆中最后一个元素后面
for( ; H->Data[i/2]->Weight > T->Weight; i/=2) //不断跟父结点比较
H->Data[i] = H->Data[i/2];
H->Data[i] = T; //将T插入
}
-最小堆的删除
bool IsEmpty(MinHeap H) //判断最小堆是否为空
{
if (H->Size == 0)
return true;
else
return false;
}
HuffmanTree DeleteMin(MinHeap H) //删除并返回最小堆中最小的元素
{
int Parent, Child;
HuffmanTree MinT, temp;
if(IsEmpty(H)) {
printf("最小堆已空");
}
MinT = H->Data[1]; //取出根结点
temp = H->Data[H->Size--]; //最小堆中的最后一个元素
for(Parent = 1; Parent*2 <= H->Size; Parent = Child) { //在有左孩子的条件下,从根结点开始向下比较
Child = Parent*2; //先让Child指向左孩子
if((Child != H->Size) && (H->Data[Child] > H->Data[Child+1])) //如果有右孩子且右孩子比左孩子小
Child++; //Child指向右孩子
if(temp <= H->Data[Child]) //如果最后这个元素比根结点的左右孩子都小,即temp是最小元素
break;
else //否则将左右孩子中小的那一个移动到根结点
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = temp;
return MinT;
}
将找到的最小结点合并——哈夫曼树的构造
链表实现
哈夫曼树的数据结构定义
typedef struct TNode *HuffmanTree;
struct TNode { //树结点定义
int Weight; //权值
HuffmanTree Left, Right; //左右孩子,类型为树结点
};
哈夫曼树的构造
HuffmanTree Huffman(MinHeap H)
{
int i;
HuffmanTree T;
BuildMinHeap(H);
for (i = 1; i < H->Size; i++) { //做Size-1次合并
T = (HuffmanTree)malloc(sizeof(struct TNode));
T->Left = DeleteMin(H); //从最小堆中删除一个结点作为新的T的左孩子
T->Right = DeleteMin(H); //从最小堆中删除一个结点作为新的T的右孩子
T->Weight = T->Left->Weight + T->Right->Weight;//计算新带权路径长度
Insert(H,T); //将新T插入最小堆
}
T = DeleteMin(H);
return T;
}
数组实现
数据结构定义
typedef struct {
char data; //结点的数据
int weight; //结点的权值
int lchild, rchild; //左、右孩子
int parent; //父结点
}HNode;
哈夫曼树的构造
void HuffmanTree(HNode hufftree[], int w[], int n)
{
for(i = 0; i < 2*n-1; i++) { //初始化
hufftree[i].parent = -1;
hufftree[i].lchild = -1;
hufftree[i].rchild = -1;
}
for(i = 0; i < n; i++) //初始化结点权重
hufftree[i].weight = -1;
for(k = 0; k < 2*n-1; k++) {
Select(hufftree, i1, i2); //待写
hufftree[i1].parent = k;
hufftree[i2].parent = k;
hufftree[k].weight = hufftree[i1].weight + hufftree[i2].weight;
hufftree[k].lchild = i1;
hufftree[k].rchild = i2;
}
}
哈夫曼编码
- 根据字符出现的频率构造哈夫曼树,使电文总长度最短
- 从根结点开始,若编码是1则往左走,编码是0则往右走,遇到叶子结点则译出一个字符