其他分享
首页 > 其他分享> > 数据结构(三)—— 树(9):哈夫曼树和哈夫曼编码

数据结构(三)—— 树(9):哈夫曼树和哈夫曼编码

作者:互联网

 

数据结构系列内容的学习目录 → \rightarrow →浙大版数据结构学习系列内容汇总。

 

 

9. 哈夫曼树和哈夫曼编码

9.1 什么是哈夫曼树

  给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

  例: 将百分制的考试成绩转换成五分制的成绩。

if(score < 60 ) grade =1;
else if(score <70 ) grade =2;
else if( score < 80 ) grade =3;
else if( score <90 ) grade =4;
else grade =5;

  判定树如下图所示。

在这里插入图片描述
  如果考虑学生成绩的分布的概率:

分数段 0-59 60-69 70-79 80-89 90-100
比例 0.05 0.15 0.40 0.30 0.10

  查找效率:0.05×1+0.15×2+0.4×3+0.3×4+0.1×4= 3.15。

  修改判定树为如下图所示。

在这里插入图片描述

if(score <80)
{
    if(score < 70)
        if(score < 60) grade =1;
        else grade = 2;
    else grad=3;
}
else if(score < 90) grade =4;
else grade =5;

  查找效率:0.05×3+0.15×3+0.4×2+0.3×2+0.1×2=2.2。

  哈夫曼树要解决的问题: 如何根据结点不同的查找频率构造更有效的搜索树?

  哈夫曼树的定义:
    ⋄ \diamond ⋄ 带权路径长度(WPL): 设二叉树有n个叶子结点,每个叶子结点带有权值 w k w_{k} wk​,从根结点到每个叶子结点的长度为 l k l_{k} lk​,则每个叶子结点的带权路径长度之和就是 W P L = ∑ k = 1 n w k l k WPL=\sum _{k=1}^{n}w_{k}l_{k} WPL=∑k=1n​wk​lk​
    ⋄ \diamond ⋄ 最优二叉树或哈夫曼树: WPL最小的二叉树。

  例: 有五个叶子结点,它们的权值为{1,2,3,4,5},用此权值序列可以构造出形状不同的多个二叉树,如下图所示。

在这里插入图片描述

9.2 哈夫曼树的构造

  每次把权值最小的两棵二叉树合并,如下图所示。

在这里插入图片描述
  哈夫曼树的构造代码如下所示。

typedef struct TreeNode *HuffmanTree;
struct TreeNode {
    int weight;
    HuffmanTree Left,Right;
}    
HuffmanTree Huffman(MinHeap H)
{  //假设H->Size个权值已经存在H->Elements[]->weight里
    int i;
    HuffmanTree T;
    BuilGMinHeap(H);  //将H->Elements[]按权值调整为最小堆
    for(i = 1; i <H->Size; i++)  //做H->Size-1次合并
    {  
        T = malloc(sizeof(struct TreeNode));  //建立新结点
        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;
}

  整体复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)。

  哈夫曼树的特点:1. 没有度为 1 的结点;
          2. n 个叶结点的哈夫曼树共有 2n-1 个结点;
            n 0 n_{0} n0​:叶结点总数
            n 1 n_{1} n1​:只有一个儿子的结点总数(没有度为 1 的结点, n 1 = 0 n_{1}=0 n1​=0)
            n 2 n_{2} n2​:有2个儿子的结点总数( n 2 = n 0 − 1 n_{2}=n_{0}-1 n2​=n0​−1)
            总结点数: n 0 + n 1 + n 2 = 2 n 0 − 1 n_{0}+n_{1}+n_{2} = 2n_{0} -1 n0​+n1​+n2​=2n0​−1
          3. 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树;
          4. 对同一组权值 { w 1 , w 2 , . . . , w n } \{ w_{1}, w_{2}, ..., w_{n} \} {w1​,w2​,...,wn​},是否存在不同构的两棵哈夫曼树呢? 答案是可能的!
            例: 对一组权值{1,2,3,3},不同构的两棵哈夫曼树如下图所示。

在这里插入图片描述

9.3 哈夫曼编码

  给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最少?

  例: 假设有一段文本,包含58个字符,并由以下7个字符构:a,e,i,s, t,空格(sp),换行(nl) ;这7个字符出现的次数不同。如何对这7个字符进行编码,使得总编码空间最少?
  分析: (1)用等长ASCII编码:58×8= 464位;
      (2)用等长3位编码:58×3=174位;
      (3)不等长编码:出现频率高的字符用的编码短些,出现频率低的字符则可以编码长些?

  问题: 怎么进行不等长编码? 如何避免二义性?
  前缀码prefix code: 任何字符的编码都不是另一字符编码的前缀,可以无二义地解码。

  二叉树用于编码:(1)左右分支:0、1;
          (2)字符只在叶结点上。

  例: 四个字符的频率:a:4, u:1,x:2,z:1,如下图所示。

在这里插入图片描述
  怎么构造一颗编码代价最小的二叉树?

  对上面例子中包含58个字符的一段文本进行编码,使得总编码空间最少,如下图所示。

C i C_{i} Ci​ a e i s t sp nl
f i f_{i} fi​ 10 15 12 3 4 13 1

在这里插入图片描述

9.4 最小堆实现哈夫曼树

  最小堆实现哈夫曼树代码如下所示。

#include<iostream>
using namespace std;
#define MaxSize 1000
#define MinData -1000 
int A[] = { 1,3,5,8 };  // 预先定义好一组权值 
int A_length = 4;  // 定义其长度 
typedef struct HeapStruct *MinHeap;
typedef struct TreeNode *HuffmanTree;
struct HeapStruct {  // 存放哈夫曼树的堆 
	HuffmanTree *data;   // 存值的数组  
	int size;   // 堆的当前大小  
	int capacity; // 最大容量	
};
struct TreeNode { // 哈夫曼树 
	int weight;  //权值
	HuffmanTree Left;  // 左子树 
	HuffmanTree right; // 右子树 
};

MinHeap Create_H(); // 初始化堆
HuffmanTree Create_T(); // 初始化哈夫曼树 
void sort(MinHeap H, int i); // 调整子最小堆 
void adjust(MinHeap H); // 调整最小堆 
void BuildMinHeap(MinHeap H);  // 建堆 
HuffmanTree Delete(MinHeap H); // 删除最小堆元素 
void Insert(MinHeap H, HuffmanTree Huff);  // 插入最小堆元素 
void PreOrderTraversal(HuffmanTree Huff); // 先序遍历 
HuffmanTree Huffman(MinHeap H); // 哈夫曼树的构建 

// 初始化堆
MinHeap Create_H() 
{
	MinHeap H;
	HuffmanTree Huff;
	H = (MinHeap)malloc(sizeof(struct HeapStruct));
	H->data = (HuffmanTree *)malloc(sizeof(struct TreeNode) * (MaxSize + 1));
	H->capacity = MaxSize;
	H->size = 0;
	// 给堆置哨兵 
	Huff = Create_T();  // 初始化哈夫曼树 
	Huff->weight = MinData;
	H->data[0] = Huff;
	return H;
}

// 初始化哈夫曼树 
HuffmanTree Create_T() 
{
	HuffmanTree Huff;
	Huff = (HuffmanTree)malloc(sizeof(struct TreeNode));
	Huff->weight = 0;
	Huff->Left = NULL;
	Huff->right = NULL;
	return Huff;
}

// 调整子最小堆 
void sort(MinHeap H, int i) 
{
	int Parent, Child;
	int temp = H->data[i]->weight; // 取出当前"根结点"值
	for (Parent = i; Parent * 2 <= H->size; Parent = Child) 
	{
		Child = 2 * Parent;
		if ((Child != H->size) && (H->data[Child + 1]->weight < H->data[Child]->weight))
			Child++;
		if (H->data[Child]->weight >= temp)
			break;
		else
			H->data[Parent] = H->data[Child];
	}
	H->data[Parent]->weight = temp;
}

// 调整最小堆 
void adjust(MinHeap H) 
{
	for (int i = H->size / 2; i > 0; i--)
		sort(H, i);// 每个"子最小堆"调整 
}

// 建堆 
void BuildMinHeap(MinHeap H) 
{
	// 将权值读入堆中
	HuffmanTree Huff;
	for (int i = 0; i < A_length; i++) 
	{
		Huff = Create_T();
		Huff->weight = A[i];
		H->data[++H->size] = Huff;
	}
	// 调整堆 
	adjust(H);
}

// 删除最小堆元素
HuffmanTree Delete(MinHeap H) 
{
	int Parent, Child;
	HuffmanTree T = H->data[1];  // 取出根结点的哈夫曼树 
	HuffmanTree temp = H->data[H->size--]; // 取出最后一个结点哈夫曼树的权值 
	for (Parent = 1; Parent * 2 <= H->size; Parent = Child) 
	{
		Child = 2 * Parent;
		if ((Child != H->size) && (H->data[Child + 1]->weight < H->data[Child]->weight))
			Child++;
		if (H->data[Child]->weight >= temp->weight)
			break;
		else
			H->data[Parent] = H->data[Child];
	}
	H->data[Parent] = temp;
	// 构造一个 HuffmanTree 结点,附上刚才取出来的权值,返回该结点 
	return T;
}

// 插入一个哈夫曼树
void Insert(MinHeap H, HuffmanTree Huff) 
{
	int weight = Huff->weight; // 取出权值
	int i = ++H->size;
	for (; H->data[i / 2]->weight > weight; i /= 2)
		H->data[i] = H->data[i / 2];
	H->data[i] = Huff;
}

//先序遍历 
void PreOrderTraversal(HuffmanTree Huff) 
{
	if (Huff) 
	{
		cout << Huff->weight << " ";
		PreOrderTraversal(Huff->Left);
		PreOrderTraversal(Huff->right);
	}
}

// 哈夫曼树的构造 
HuffmanTree Huffman(MinHeap H) 
{
	HuffmanTree T;
	BuildMinHeap(H); // 建堆 
	int times = H->size;
	// 做 times-1 次合并 
	for (int i = 1; i < times; i++) 
	{
		T = (HuffmanTree)malloc(sizeof(struct TreeNode));
		T->Left = Delete(H);   // 从堆中删除一个结点,作为新T的左子结点 
		T->right = Delete(H);  // 从堆中删除一个结点,作为新T的右子结点 
		T->weight = T->Left->weight + T->right->weight; // 重新计算权值 
		Insert(H, T);  // 再加进堆中 
	}
	T = Delete(H);
	return T;
}

int main() 
{
	MinHeap H;
	HuffmanTree Huff;
	H = Create_H();
	Huff = Huffman(H);
	PreOrderTraversal(Huff);
	cout << endl;
	system("pause");
	return 0;
}

  代码运行结果如下图所示。

在这里插入图片描述

 

标签:编码,结点,哈夫曼,weight,Huff,HuffmanTree,数据结构,data
来源: https://blog.51cto.com/u_15178976/2981919