哈夫曼编码原理及实现

文章目录

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

一.哈夫曼编码原理

哈夫曼编码(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.rhkb.cn/news/134980.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV(四十一):图像分割-分水岭法

1.分水岭方法介绍 OpenCV 提供了分水岭算法&#xff08;Watershed Algorithm&#xff09;的实现&#xff0c; 使用分水岭算法对图像进行分割&#xff0c;将图像的不同区域分割成互不干扰的区域。分水岭算法模拟了水在图像中的扩散和聚集过程&#xff0c;将标记的边界被看作是阻…

PHP8中获取并删除数组中第一个元素-PHP8知识详解

我在上一节关于数组的教程&#xff0c;讲的是在php8中获取并删除数组中最后一个元素&#xff0c;今天分享的是相反的&#xff1a;PHP8中获取并删除数组中第一个元素。 回顾一下昨天的知识&#xff0c;array_pop()函数将返回数组的最后一个元素&#xff0c;今天学习的是使用arr…

Vue自动生成二维码并可下载二维码

遇到一个需求&#xff0c;需要前端自行生成用户的个人名片分享二维码&#xff0c;并提供二维码下载功能。在网上找到很多解决方案&#xff0c;最终吭哧吭哧做完了&#xff0c;把它整理记录一下&#xff0c;方便后续学习使用&#xff01;嘿嘿O(∩_∩)O~ 这个小东西有以下功能特点…

AWT中常用组件

笔记&#xff1a;https://www.yuque.com/huangzhanqi/rhwoir/repuodh23fz01wiv 仓库&#xff1a;Java图形化界面: Java图形化界面学习demo与资料 (gitee.com) 基本组件 组件名 功能 Button Button Canvas 用于绘图的画布 Checkbox 复选框组件&#xff08;也可当做单选…

批量获取CSDN文章对文章质量分进行检测,有助于优化文章质量

&#x1f4da;目录 ⚙️简介✨分析获取步骤⛳获取文章列表☘️前期准备✨ 接口解析⚡️ 获取文章的接口 ☄️文章质量分接口⭐接口分析 ⌛代码实现&#xff1a;⚓核心代码:⛵测试用例:⛴ 运行效果:☘️增加Excel导出 ✍️结束 ⚙️简介 有时候我们写文章是为了记录当下遇到的bu…

查看表结构

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: desc 表名; 描述: 如果表不存在,就提示不存在; 如果表存在,就显示表的结构 比如: desc test01; desc test02; 错误示范: mysql> …

systemserver的inputdispatcher直接产生CANCEL事件原理分析-讨厌的android触摸面试题

背景回顾&#xff1a; 上一个blog已经重点讲解了app层面自己产生的Cancel触摸事件&#xff0c;大概产生的原理如下&#xff1a; 上一个blog地址&#xff1a;https://blog.csdn.net/learnframework/article/details/124086882 即可以看出来&#xff0c;在服务端systemserver其实…

vue3-vant4-vite-pinia-axios-less学习日记

代码地址 GitHub&#xff1a;vue3-vant4-vite-pinia-axios-less 效果如图 1.首页为导航栏 2.绑定英雄页 3.注册页 4.英雄列表页 5.后面不截图了&#xff0c;没啥了 模块 1.vant4&#xff1a;按需引入组件样式文档 2.安装该vite-plugin-vue-setup-extend插件可以直接在…

基于Java+SpringBoot+Vue的图书借还小程序的设计与实现(亮点:多角色、点赞评论、借书还书、在线支付)

图书借还管理小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1 小…

Linux安全加固:保护你的服务器

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【深度学习实验】线性模型(三):使用Pytorch实现简单线性模型:搭建、构造损失函数、计算损失值

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 定义线性模型linear_model 2. 定义损失函数loss_function 3. 定义数据 4. 调用模型 5. 完整代码 一、实验介绍 使用Pytorch实现 线性模型搭建构造损失函数计算损失值 二、…

TensorFlow与pytorch特定版本虚拟环境的安装

TensorFlow与Python的版本对应&#xff0c;注意&#xff0c;一定要选择对应的版本&#xff0c;否则会让你非常痛苦&#xff0c;折腾很久搞不清楚原因。 建议使用国内镜像源安装 没有GPU后缀的就表示是CPU版本的&#xff0c;不加版本就是最新 pip install tensorflow -i https:…

Learn Prompt-人工智能基础

什么是人工智能&#xff1f;很多人能举出很多例子说这就是人工智能&#xff0c;但是让我们给它定义一个概念大家又觉得很难描述的清楚。实际上&#xff0c;人工智能并不是计算机科学领域专属的概念&#xff0c;在其他学科包括神经科学、心理学、哲学等也有人工智能的概念以及相…

Vue3+ElementUI使用

<!DOCTYPE html> <html> <head><meta charset"UTF-8"><meta name"viewport" content"initial-scale1.0,maximum-scale1.0,minimum-scale1.0,user-scalable0, widthdevice-width"/><!-- 引入样式 --><lin…

《C和指针》笔记24: 指针和间接访问

本文主要讲指针和间接访问&#xff0c;标题对应《C和指针对应的章节》&#xff0c;引用的地方是自己写的一些注释、理解和总结。 指针、间接访问和左值 先回顾一下左值和右值 左值代表着一个位置。右值代表着一个值。赋值等号左边是个左值&#xff0c;赋值等号右边是一个右值…

Vue入门简介(带你打开Vue的大门)

目录 前言 一、Vue简介 1. 什么是Vue 2. Vue的应用场景 3. Vue的作用&#xff08;重要性&#xff09; 4. 什么是MVVM模式 5. 开源库网址 二、Vue入门使用 1. 基础使用步骤 1.1 引入Vue.js 1.2 创建Vue实例 1.3 编写Vue模板 1.4 数据绑定与指令 1.5 调用Vue方法和…

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

flutter聊天界面-TextField输入框buildTextSpan实现功能展示高亮功能 最近有位朋友讨论的时候&#xff0c;提到了输入框的高亮展示。在flutter TextField中需要插入特殊样式的标签&#xff0c;比如&#xff1a;“请 张三 回答一下”&#xff0c;这一串字符在TextField中输入&a…

群辉 Synology NAS Docker 安装 RustDesk-server 自建服务器只要一个容器

from https://blog.zhjh.top/archives/M8nBI5tjcxQe31DhiXqxy 简介 之前按照网上的教程&#xff0c;rustdesk-server 需要安装两个容器&#xff0c;最近想升级下版本&#xff0c;发现有一个新镜像 rustdesk-server-s6 可以只安装一个容器。 The S6-overlay acts as a supervi…

NSS [HNCTF 2022 WEEK2]ohmywordpress(CVE-2022-0760)

NSS [HNCTF 2022 WEEK2]ohmywordpress&#xff08;CVE-2022-0760&#xff09; 题目描述&#xff1a;flag在数据库里面。 开题&#xff1a; 顺着按钮一直点下去会发现出现一个按钮叫安装WordPress 安装完之后的界面&#xff0c;有一个搜索框。 F12看看network。 又出现了这个…

day22集合01

1.Collection集合 1.1数组和集合的区别【理解】 相同点 都是容器,可以存储多个数据 不同点 数组的长度是不可变的,集合的长度是可变的 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 1.2集合类体系结构【理解】…