数据结构:哈夫曼树及其哈夫曼编码

2024-06-07 21:28

本文主要是介绍数据结构:哈夫曼树及其哈夫曼编码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

        1.哈夫曼树是什么?

        2.哈夫曼编码是什么?

        3.哈夫曼编码的应用

        4.包含头文件

        5.结点设计

        6.接口函数定义

        7.接口函数实现

        8.哈夫曼编码测试案列


哈夫曼树是什么?

        哈夫曼树(Huffman Tree)是一种特殊的二叉树,由David A. Huffman在1952年发明的,用于数据压缩领域。哈夫曼树是一种最优的二叉树,因为它具有最小的加权路径长度。这里的“最优”是指在给定的一组权重(通常是字符出现频率)下,哈夫曼树的加权路径长度(即树中所有叶节点的权重乘以其到根节点的距离)是最小的,以下是哈夫曼树的特点:

        1.完全二叉树:除了最后一层外,每一层都是满的

        2.加权路径长度最小:所有叶节点的权重乘以其到根节点的距离之和是最小的

        3.每个节点都有权重:叶节点代表单个字符,非叶节点代表字符的集合


哈夫曼编码是什么?

        哈夫曼编码是一种使用哈夫曼树进行编码的方法。它将每个字符映射为一个唯一的二进制串,这些二进制串的长度不同,且是根据字符出现频率来确定的。频率越高的字符,其编码越短;频率越低的字符,其编码越长。这种编码方式可以有效地减少数据的存储空间或传输时间。实现哈夫曼编码的步骤如下:

        1.统计字符频率:首先统计数据集中每个字符出现的频率

        2.构建哈夫曼树

                1.将每个字符及其频率作为叶子节点放入优先队列(通常是最小堆)

                2.从队列中取出两个权重最小的节点,创建一个新的内部节点,其权重为这两个节点权重之和

                3.将新节点重新加入队列。重复上述步骤,直到队列中只剩下一个节点,这个节点就是哈夫曼树的根节点

        3.生成编码:从根节点开始,向左子树走标记为0,向右子树走标记为1,直到到达叶节点,此时叶节点对应的字符的路径标记就是其哈夫曼编码


哈夫曼编码的应用

        哈夫曼编码是一种非常实用的编码技术,它通过利用数据的内在特性来优化存储和传输效率:

        1.数据压缩:用于无损数据压缩,特别是在文本压缩中非常有效。

        2.文件压缩:如ZIP文件格式就使用了哈夫曼编码。

        3.通信协议:在某些通信协议中,用于减少传输数据的大小。


包含头文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

结点设计

#define Initsize 100
typedef int Elemtype;
int Node[Initsize][2];				//定义二维数组Node存储输入的字符和字符所含的权值
int NodeValue[Initsize];			//定义一维数组NodeValue存储经排序过的字符的权值
int Hand = 0;						//定义整形变量Hand作为数组NodeValue的头指针
int CodeHead = 0;					//定义int类型变量CodeHead作为指针数组Code的头指针typedef struct HTree {Elemtype value;					//存储结点权值Elemtype Lvalue, Rvalue;		//存储孩子标识struct HTree* lchild;			//存储左孩子树struct HTree* rchild;			//存储右孩子树
}HTree,*HfmTree;HfmTree Head;						//定义全局变量Head作为哈夫曼树的根节点指针
HfmTree Code[Initsize];				//定义HTree类型的指针数组Code,存储结点的地址

接口函数定义

void InitHTree(HfmTree& A);		//用于初始化哈夫曼树
void InsertNode(int A);			//用于输入字符和其权值
void SortNodeV(int A);			//用于对输入的权值进行排序
void InitLHfm(HfmTree& A);		//用于哈夫曼树的左子树进行初始化并赋值
void InitRHfm(HfmTree& A);		//用于哈夫曼树的右子树进行初始化并赋值
void InsertHTree(HfmTree& A,int B); //用于创建哈夫曼树
void PostOrder(HfmTree A);	        //用于对哈夫曼树进行后序遍历
void InputBTree(HfmTree A);	        //用于对哈夫曼树的结点权值输出
void SeekHTreeL(HfmTree A, int B);	//用于单独寻找哈夫曼树的左子树的字符及权值
void SeekHTreeR(HfmTree A, int B);	//用于寻找哈夫曼树的右子树的字符及权值
void InputHfmCode(HfmTree A,int B);	//用于输出哈夫曼编码
void InitRootHfm(HfmTree& A,HfmTree &B,HfmTree &C); //用于对哈夫曼树的根结点进行初始化并赋值

接口函数实现

void InputHfmCode(HfmTree A,int B) { //用于输出哈夫曼编码int i,j;while (A != NULL) {				 //对哈夫曼树的左子树进行进栈操作Code[CodeHead] = A;A = A->lchild;CodeHead++;}printf("\n");SeekHTreeL(A, B);		//使用函数SeekHtreeL对其哈夫曼树进行寻找左子树的字符及权值for (i = 1; i < CodeHead; i++) {printf("%d", Code[i]->Lvalue);	//对栈里结点所含的Lvalue进行输出(从根结点开始输出其含有的LvaLue)}CodeHead--;					//出栈操作while (CodeHead != 0) {		//判断栈是否为空printf("\n");SeekHTreeR(A, B);		//使用函数SeekHTreeR对其哈夫曼树进行寻找右子树的字符及权值for (i = 0; i <= CodeHead - 1; i++) {	//对栈里结点所含的Lvalue进行输出(从根结点开始输出其含有的LvaLue)if (i == CodeHead - 1) {				//若为栈尾结点则输出其右子树printf("%d", Code[i]->Rvalue);	break;}printf("%d", Code[i]->Lvalue);}CodeHead--;				//出栈操作}
}void SeekHTreeR(HfmTree A, int B) {	//用于寻找哈夫曼树的右子树的字符及权值int i,j;for (i = CodeHead - 1; i >= 0; i++){ //遍历栈if (i == CodeHead-1) {			 //判断是否为栈的倒数第二个结点(跟定义的结点的有关)for (j = 0; j < B; j++)	{	 //遍历存储字符及权值的数组,寻找对应的字符和权值if (Node[j][1] == Code[i]->rchild->value) {Node[j][1] = -1;	//未防止字符不一样,但权值相同的出现,造成输出哈夫曼编码错误printf("%c的哈夫曼编码为:", Node[j][0]);break;					}}}break;			}
}void SeekHTreeL(HfmTree A, int B){	//用于单独寻找哈夫曼树的左子树的字符及权值int i,j;for (i = 0; i < CodeHead; i++) {	//遍历栈if (i == CodeHead - 1) {		//判断是否为栈的倒数第二个结点(跟定义的结点的有关)for (j = 0; j < B; j++) {	//遍历存储字符及权值的数组,寻找对应的字符和权值if (Node[j][1] == Code[i]->value) {	Node[j][1] = -1;	//未防止字符不一样,但权值相同的出现,造成输出哈夫曼编码错误printf("%c的哈夫曼编码为:", Node[j][0]);break;}}}}
}void InputBTree(HfmTree A) {	//用于对哈夫曼树的结点权值输出		printf("%d   ", A->value);
}void PostOrder(HfmTree A) {		//用于对哈夫曼树进行后序遍历		if (A != NULL) {PostOrder(A->lchild);	PostOrder(A->rchild);	InputBTree(A);			}
}void InsertHTree(HfmTree& A,int B) {//用于创建哈夫曼树if(Hand<B-1){				//判断是否已将所有字符及权值进行构建对应的哈夫曼树的结点if (A == NULL) {		HfmTree Q = (HTree*)malloc(sizeof(HTree));		HfmTree W = (HTree*)malloc(sizeof(HTree));		InitLHfm(Q);				//使用函数InitLHfm对其左子树初始化Hand++;InitRHfm(W);				//使用函数InitRHfm对其右子树初始化InitRootHfm(A, Q, W);		//使用函数InitRootHfm对其根结点初始化Head = A;					//更新哈夫曼树的头指针的指向InsertHTree(A, B);			}else {HfmTree Q = (HTree*)malloc(sizeof(HTree));		HfmTree W = (HTree*)malloc(sizeof(HTree));		Hand++;InitRHfm(Q);				//使用函数InitRHfm对其右子树初始化InitRootHfm(W, A, Q);		//使用函数InitRootHfm对其根结点初始化Head = W;InsertHTree(W, B);}}
}void InitRootHfm(HfmTree& A, HfmTree& B, HfmTree& C) {//用于对哈夫曼树的根结点进行初始化并赋值A = (HTree*)malloc(sizeof(HTree));A->value = B->value + C->value;			//根结点的权值为两个子结点的权值之和A->Lvalue = 1;							//添加左子树标识A->Rvalue = 0;A->lchild = B;							//添加根结点指向的左子树A->rchild = C;							//添加根结点指向的右子树printf("新建的根结点的权值数据为%d\n", A->value);
}void InitRHfm(HfmTree& A) {		//用于哈夫曼树的右子树进行初始化并赋值A->value = NodeValue[Hand];	//对结点所含的权值进行更新A->Lvalue = 0;A->Rvalue = 1;				//添加右子树标识A->lchild = NULL;						A->rchild = NULL;printf("新建的右孩子的权值数据为%d\n", A->value);
}void InitLHfm(HfmTree& A) {			//用于哈夫曼树的左子树进行初始化并赋值A->value = NodeValue[Hand];		//对结点所含的权值进行更新A->Lvalue = 1;					//添加左子树标识A->Rvalue = 0;A->lchild = NULL;A->rchild = NULL;printf("新建的左孩子的权值数据为%d\n", A->value);
}void SortNodeV(int A) {				//用于对输入的权值进行排序int i, j, Q;for (i = 0; i < A - 1; i++) {				//冒泡排序for (j = 0; j < A - 1 - i; j++)if (NodeValue[j] > NodeValue[j + 1]) {Q = NodeValue[j];NodeValue[j] = NodeValue[j + 1];NodeValue[j + 1] = Q;}}
}void InsertNode(int A) {			//用于输入字符和其权值int i, j;char Q;for (i = 0; i < A; i++) {j = 0;printf("请输入结点的字符");getchar();			//清除缓冲区,防止赋值脏数据Q=getchar();Node[i][j] = (int) Q;j++;printf("请输入结点的权值");scanf_s("%d", &Node[i][j]);NodeValue[i] = Node[i][j];}
}void InitHTree(HfmTree& A) {		//用于初始化哈夫曼树A = NULL;printf("初始化哈夫曼树成功\n");
}

哈夫曼编码测试案列

void main() {int NodeSize,i;HfmTree X;InitHTree(X);printf("请问需要输入多少个字符");scanf_s("%d", &NodeSize);InsertNode(NodeSize);SortNodeV(NodeSize);InsertHTree(X, NodeSize);printf("创建哈夫曼树成功\n");printf("后序遍历的哈夫曼树为:");PostOrder(Head);printf("\n");InputHfmCode(Head,NodeSize);
}

这篇关于数据结构:哈夫曼树及其哈夫曼编码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040339

相关文章

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

form表单提交编码的问题

浏览器在form提交后,会生成一个HTTP的头部信息"content-type",标准规定其形式为Content-type: application/x-www-form-urlencoded; charset=UTF-8        那么我们如果需要修改编码,不使用默认的,那么可以如下这样操作修改编码,来满足需求: hmtl代码:   <meta http-equiv="Conte

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽  “弃工从研”的路上很孤独,于是我记下了些许笔记相伴,希望能够帮助到大家    知识点 1. 散列查找 散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(

浙大数据结构:树的定义与操作

四种遍历 #include<iostream>#include<queue>using namespace std;typedef struct treenode *BinTree;typedef BinTree position;typedef int ElementType;struct treenode{ElementType data;BinTree left;BinTre

Python 内置的一些数据结构

文章目录 1. 列表 (List)2. 元组 (Tuple)3. 字典 (Dictionary)4. 集合 (Set)5. 字符串 (String) Python 提供了几种内置的数据结构来存储和操作数据,每种都有其独特的特点和用途。下面是一些常用的数据结构及其简要说明: 1. 列表 (List) 列表是一种可变的有序集合,可以存放任意类型的数据。列表中的元素可以通过索

浙大数据结构:04-树7 二叉搜索树的操作集

这道题答案都在PPT上,所以先学会再写的话并不难。 1、BinTree Insert( BinTree BST, ElementType X ) 递归实现,小就进左子树,大就进右子树。 为空就新建结点插入。 BinTree Insert( BinTree BST, ElementType X ){if(!BST){BST=(BinTree)malloc(sizeof(struct TNo