ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-07-05 15:51:31  阅读:182  来源: 互联网

标签:编码 结点 哈夫曼 weight Huff HuffmanTree 数据结构 data


 

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

 

  • 9. 哈夫曼树和哈夫曼编码
    • 9.1 什么是哈夫曼树
    • 9.2 哈夫曼树的构造
    • 9.3 哈夫曼编码
    • 9.4 最小堆实现哈夫曼树

 

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有