哈夫曼编码原理及实现

news2024/12/23 9:52:53

文章目录

  • 一.哈夫曼编码原理
    • 哈夫曼二叉树构建
  • 二.具体代码实现

一.哈夫曼编码原理

哈夫曼编码(Huffman Coding)是一种用于数据压缩的编码方法,它通过给出不同的数据符号分配不同长度的编码,使得出现频率高的符号具有较短的编码,而出现频率低的符号具有较长的编码,从而达到压缩数据的目的。

哈夫曼编码的原理可以通过以下步骤来解释:

统计频率:首先,需要统计待编码数据中每个符号的出现频率。符号可以是字符、字节或其他数据单元。统计频率可以通过遍历整个数据集来完成,并记录每个符号出现的次数。

构建编码树:根据符号的频率构建一个特殊的二叉树,称为哈夫曼树(Huffman Tree)或编码树。构建编码树的方法是将频率最低的两个符号合并为一个新节点,该节点的频率为两个节点频率之和。将新节点插入到已有节点的集合中,重复这个步骤,直到只剩下一个节点,即根节点为止。在构建过程中,可以使用优先队列或最小堆来维护频率最低的节点。

分配编码:从根节点开始,沿着左子树走为0,沿着右子树走为1,将0和1分别分配给左右子节点。重复这个过程,直到遍历到每个叶子节点为止。每个叶子节点的路径上的0和1的序列就是对应符号的哈夫曼编码。

生成编码表:将每个符号及其对应的哈夫曼编码存储在一个编码表中,以备后续的编码和解码使用。

进行编码:将原始数据中的每个符号替换为其对应的哈夫曼编码,生成压缩后的编码数据。由于频率高的符号具有较短的编码,而频率低的符号具有较长的编码,所以整个编码后的数据长度会相对减小。

哈夫曼编码的优点是没有冗余和歧义性,即每个编码都不是其他编码的前缀,这种性质被称为前缀码。这使得编码和解码过程都是非常高效的。然而,对于哈夫曼编码的最佳性能,符号的频率应该是根据数据集的统计特征进行调整的。

哈夫曼编码举例: 假设要对“we will we will r u”进行压缩
压缩前,使用 ASCII 码保存
在这里插入图片描述
共需: 19 个字节 - 152 个位保存

下面我们先来统计这句话中每个字符出现的频率。如下表,按频率高低已排序:
在这里插入图片描述
接下来,我们按照字符出现的频率,制定如下的编码表:
在这里插入图片描述
这样,“we will we will r u”就可以按如下的位来保存:
01 110 10 01 1111 00 00 10 01 110 10 01 1111 00 00 10 11101 10 11100
在这里插入图片描述

哈夫曼二叉树构建

1.按出现频率高低将其放入一个数组中,从左到右依次为频率逐渐增加
在这里插入图片描述
在这里插入图片描述
2. 从左到右进行合并,依次构建二叉树。第一步取前两个字符 u 和 r 来构造初始二叉树,第一个字符作为左节点,第二个元素作为右节点,然后两个元素相加作为新的空元素,并且两者权重相加作为新元素的权重。
在这里插入图片描述
3.新节点加入后,依据权重重新排序,按照权重从小到大排列,上图已有序。
4.红色区域的新增元素可以继续和 i 合并,如下图所示:
在这里插入图片描述
5. 合并节点后, 按照权重从小到大排列,如下图所示。

6. 排序后,继续合并最左边两个节点,构建二叉树,并且重新计算新节点的权重
在这里插入图片描述
7. 重新排序
在这里插入图片描述
8. 重复上面步骤 6 和 7,直到所有字符都变成二叉树的叶子节点
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二.具体代码实现

Huff.h

#pragma once
#pragma once
#include <stdio.h>
#include <assert.h>
#include <Windows.h>
#include <iostream>
#include <iomanip>

using namespace std;
#define MaxSize 1024 //队列的最大容量
typedef struct _Bnode
{
	char value;
	int weight;
	struct _Bnode* parent;
	struct _Bnode* lchild;
	struct _Bnode* rchild;
} Btree, Bnode; /* 结点结构体 */
typedef Bnode* DataType; //任务队列中元素类型
typedef struct _QNode { //结点结构
	int priority; //每个节点的优先级,数值越大,优先级越高,优先级相同,取第一个节点
	DataType data;
	struct _QNode* next;
}QNode;
typedef QNode* QueuePtr;
typedef struct Queue
{
	int length; //队列的长度
	QueuePtr front; //队头指针
	QueuePtr rear; //队尾指针
}LinkQueue;

//队列初始化,将队列初始化为空队列897943840118979438401111
void InitQueue(LinkQueue* LQ)
{
	if (!LQ) return;
	LQ->length = 0;
	LQ->front = LQ->rear = NULL; //把对头和队尾指针同时置 0
}
//判断队列为空
int IsEmpty(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->front == NULL)
	{
		return 1;
	}
	return 0;
}
//判断队列是否为满
int IsFull(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->length == MaxSize)
	{
		return 1;
	}
	return 0;
}
//入队,将元素 data 插入到队列 LQ 中
int EnterQueue(LinkQueue* LQ, DataType data, int priority) {
	if (!LQ) return 0;
	if (IsFull(LQ)) {
		cout << "无法插入元素 " << data << ", 队列已满!" << endl;
		return 0;
	}
	QNode* qNode = new QNode;
	qNode->data = data;
	qNode->priority = priority;
	qNode->next = NULL;
	if (IsEmpty(LQ)) {//空队列
		LQ->front = LQ->rear = qNode;
	}
	else {
		qNode->next = LQ->front;
		LQ->front = qNode;
		//LQ->rear->next =qNode;//在队尾插入节点 qNode
		//LQ->rear = qNode; //队尾指向新插入的节点
	}
	LQ->length++;
	return 1;
}
//出队,遍历队列,找到队列中优先级最高的元素 data 出队
int PopQueue(LinkQueue* LQ, DataType* data) {
	QNode** prev = NULL, * prev_node = NULL;//保存当前已选举的最高优先级节点上一个节点的指针地址。
	QNode* last = NULL, * tmp = NULL;
	if (!LQ || IsEmpty(LQ)) {
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	//prev 指向队头 front 指针的地址
	prev = &(LQ->front);
	//printf("第一个节点的优先级: %d\n", (*prev)->priority);
	last = LQ->front;
	tmp = last->next;
	while (tmp) {
		if (tmp->priority < (*prev)->priority) {
			//printf("抓到个更小优先级的节点[priority: %d]\n",tmp->priority);
			prev = &(last->next);
			prev_node = last;
		}
		last = tmp;
		tmp = tmp->next;
	}
	*data = (*prev)->data;
	tmp = *prev;
	*prev = (*prev)->next;
	delete tmp;
	LQ->length--;
	//接下来存在 2 种情况需要分别对待
	//1.删除的是首节点,而且队列长度为零
	if (LQ->length == 0) {
		LQ->rear = NULL;
	}
	//2.删除的是尾部节点
	if (prev_node && prev_node->next == NULL) {
		LQ->rear = prev_node;
	}
	return 1;
}
//打印队列中的各元素
void PrintQueue(LinkQueue* LQ)
{
	QueuePtr tmp;
	if (!LQ) return;
	if (LQ->front == NULL) {
		cout << "队列为空!";
		return;
	}
	tmp = LQ->front;
	while (tmp)
	{
		cout << setw(4) << tmp->data->value << "[" << tmp->priority << "]";
		tmp = tmp->next;
	}
	cout << endl;
}
//获取队首元素,不出队
int GetHead(LinkQueue* LQ, DataType* data)
{
	if (!LQ || IsEmpty(LQ))
	{
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	*data = LQ->front->data;
	return 1;
}
//清空队列
void ClearQueue(LinkQueue* LQ)
{
	if (!LQ) return;
	while (LQ->front) {
		QueuePtr tmp = LQ->front->next;
		delete LQ->front;
		LQ->front = tmp;
	}
	LQ->front = LQ->rear = NULL;
	LQ->length = 0;
}
//获取队列中元素的个数
int getLength(LinkQueue* LQ) {
	if (!LQ) return 0;
	return LQ->length;
}

main.cpp

#include "Huff.h"

using namespace std;
void PreOrderRec(Btree* root);
/* 构造哈夫曼编码树 */
void HuffmanTree(Btree*& huff, int n)
{
	LinkQueue* LQ = new LinkQueue;
	int i = 0;
	//初始化队列
	InitQueue(LQ);
	/* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */
	for (i = 0; i < n; i++)
	{
		Bnode* node = new Bnode;
		cout << "请输入第" << i + 1 << "个字符和出现频率: " << endl;
		cin >> node->value; //输入字符
		cin >> node->weight;//输入权值
		node->parent = NULL;
		node->lchild = NULL;
		node->rchild = NULL;
		EnterQueue(LQ, node, node->weight);
	}
	PrintQueue(LQ);
	//system("pause");
	//exit(0);
	do {
		Bnode* node1 = NULL;
		Bnode* node2 = NULL;
		if (!IsEmpty(LQ)) {
			PopQueue(LQ, &node1);
			printf("第一个出队列的数:%c, 优先级: %d\n", node1->value,
				node1->weight);
		}
		else {
			break;
		}
		if (!IsEmpty(LQ)) {
			Bnode* node3 = new Bnode;
			PopQueue(LQ, &node2);
			printf("第二个出队列的数:%c, 优先级: %d\n", node2->value,
				node2->weight);
			node3->lchild = node1;
			node1->parent = node3;
			node3->rchild = node2;
			node2->parent = node3;
			node3->value = ' ';
			node3->weight = node1->weight + node2->weight;
			printf("合并进队列的数:%c, 优先级: %d\n", node3->value,
				node3->weight);
			EnterQueue(LQ, node3, node3->weight);
		}
		else {
			huff = node1;
			break;
		}
	} while (1);
}
/************************
* 采用递归方式实现前序遍历
*************************/
void PreOrderRec(Btree* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("- %c -", root->value);
	PreOrderRec(root->lchild);
	PreOrderRec(root->rchild);
}
int main(void) {
	Btree* tree = NULL;
	HuffmanTree(tree, 7);
	PreOrderRec(tree);
	system("pause");
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySql中分割字符串

MySql中分割字符串 在MySql中分割字符串可以用到SUBSTRING_INDEX&#xff08;str, delim, count&#xff09; 参数解说       解释 str         需要拆分的字符串 delim         分隔符&#xff0c;通过某字符进行拆分 count          当 count 为正数&…

Web 第一步:HTTP 协议(基础)

这里是JavaWeb的开头部分&#xff01;那么先解释一下吧&#xff1a; Web&#xff1a;全球广域网&#xff0c;也称为万维网&#xff08;www&#xff09;&#xff0c;能够通过浏览器访问的网站。 JavaWeb&#xff1a;是用Java技术来解决相关 Web 互联网领域的技术栈。 &#xf…

Java8中判断一个对象不为空存在一个类对象是哪个

Java8中判断一个对象不为空存在一个类对象是哪个&#xff1f; 在Java 8中&#xff0c;你可以使用java.util.Optional类来处理可能为空的对象。Optional类可以帮助你优雅地处理空值情况&#xff0c;而不需要显式地进行空值检查。 这是一个简单的Optional示例&#xff1a; imp…

蓝桥杯 题库 简单 每日十题 day3

01 约数个数 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 1200000 有多少个约数&#xff08;只计算正约数&#xff09;。 解题思路 枚举&#xff0c;从1开始一直到1200000本身都作为1200000的除数&#xff0c;…

操作系统03-IO设备管理

中断源指的就是中断请求&#xff0c;所谓中断字是指中断请求的编号。 内存不足时候&#xff0c;OS可以采取“内存紧缩”进行改善。 进程控制块PCB组织管理一般采用线性方式。 7个状态模型增加就绪挂起和阻塞挂起的状态&#xff1a;1提高效率 2 提供足够的内存 3有利于调试 生…

【电子学会】2023年05月图形化三级 -- 数星星

数星星 1. 准备工作 &#xff08;1&#xff09;删除默认角色小猫&#xff0c;添加角色Pico和Star&#xff1b; &#xff08;2&#xff09;添加背景&#xff1a;Stars。 2. 功能实现 &#xff08;1&#xff09;Pico位置在舞台左下角&#xff1b; &#xff08;2&#xff09;…

springboot和vue:三、web入门(spring-boot-starter- web+控制器+路由映射+参数传递)

spring-boot-starter- web Spring Boot将传统Web开发的mvc、json、tomcat等框架整合&#xff0c;提供了spring-boot-starter-web组件&#xff0c;简化了Web应用配置。创建SpringBoot项目勾选Spring Web选项后&#xff0c;会自动将spring-boot-starter- web组件加入到项目中。w…

Ubuntu 虚拟化中Android Studio 不支持HAXM(CPU不支持问题)

Ubuntu虚拟机中Android Studio virtual device 安装弹出如图1所示的内容时&#xff0c;解决办法就是VMware 中对处理器开启虚拟化引擎&#xff08;图2&#xff09;&#xff08;此方式是在物理设备CPU是Intel环境测试的&#xff0c;AMD的可以参考是否可行&#xff09; 查看我的文…

Python封装一个接收UDP组播的模块

Python封装一个可以接收UDP组播的类MulticastDataReceiver&#xff0c;在子线程中接收组播数据 # udp_multicast.py import socket import threading import timeclass MulticastDataReceiver:def __init__(self, multicast_group, multicast_port, dest_addr, dest_port):sel…

es小记(copy_to)

简单创建索引复制字段 1: 3个主分片,各自有一个副本,总分片数为 3*26; refresh_interval为刷新频率; 其他参数描述,转载自 PUT test1 { “settings”:{ “number_of_shards”: 1, “number_of_replicas”: 1, “refresh_interval”: “30s” }, “mappings”:{ “properties”…

C语言天花板——指针(初阶)

&#x1f320;&#x1f320;&#x1f320; 大家在刚刚接触C语言的时候就肯定听说过&#xff0c;指针的重要性以及难度等级&#xff0c;以至于经常“谈虎色变”&#xff0c;但是今天我来带大家走进指针的奇妙世界。&#x1f387;&#x1f387;&#x1f387; 一、什么是指针&…

11.外观模式

外观模式&#xff08;Facade&#xff09;&#xff0c;为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 UML 测试代码 #include <iostream> using namespace std;class SubSystemOne { pu…

Mac专用投屏工具AirServer 7 .27 for Mac中文免费激活版

AirServer 7 .27 for Mac中文免费激活版是一款Mac专用投屏工具&#xff0c;能够通过本地网络将音频、照片、视频以及支持AirPlay功能的第三方App&#xff0c;从 iOS 设备无线传送到 Mac 电脑的屏幕上&#xff0c;把Mac变成一个AirPlay终端的实用工具。 目前最新的AirServer 7.2…

Linux关于memory cgroup的几个要点

概述 本文讲述memory cgroup比较容易误解的一些逻辑&#xff0c;如果不太经常使用和解决问题的话&#xff0c;对于memory cgroup的认知会比较浅显&#xff1a;cgroup memory用来限制进程的内存使用&#xff0c;但是我们进一步想如下的问题&#xff1a; 进程的内存可以分很多类…

「UG/NX」Block UI 指定坐标SpecifyCSYS

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

JavaScript学习记录 | DOM事件流 事件冒泡-事件捕获-事件委托

目录 DOM事件流常见面试题事件冒泡与事件捕获事件冒泡使用场景事件捕获使用场景事件冒泡和事件捕获区别 事件委托 - 利用事件冒泡机制事件委托应用场景支持事件委托的事件事件委托的优缺点 DOM事件流 DOM事件流的三个阶段&#xff1a;捕获阶段 -> 目标阶段 -> 冒泡阶段 …

9月13-14日上课内容 第三章 ELK日志分析系统及部署实例

本章结构 ELK日志分析系统简介 ELK日志分析系统分为 Elasticsearch Logstash Kibana 日志处理步骤 1.将日志进行集中化管理 2.将日志格式化(Logstash) 并输出到Elasticsearch 3.对格式化后的数据进行索引和存储 (Elasticsearch) 4.前端数据的展示(Kibana) Elasticsearch介…

Maven 工具学习笔记(基础)

Maven 是专门用于管理和构建Java项目的工具&#xff0c;其主要功能提供有&#xff1a; 标准化的项目结构&#xff08;在不同IDE之间其项目结构不一样&#xff0c;代表不能通用&#xff09;标准化的构建流程&#xff08;编译 ——> 测试 ——> 打包 ——> 发布...&…

epoll实现TCP的服务器与客户端通信

服务器&#xff1a; #include<myhead.h> #define IP "192.168.250.100" #define PORT 8888 /* typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64; } epoll_data_t;struct epoll_event {uint32_t events; …

xss渗透(跨站脚本攻击)

一、什么是XSS? XSS全称是Cross Site Scripting即跨站脚本&#xff0c;当目标网站目标用户浏览器渲染HTML文档的过程中&#xff0c;出现了不被预期的脚本指令并执行时&#xff0c;XSS就发生了。 这里我们主要注意四点&#xff1a; 1、目标网站目标用户&#xff1b; 2、浏览…