算法学习笔记(7.6)-贪心算法(霍夫曼编码)

目录

1.什么是霍夫曼树

2.霍夫曼树的构造过程

3.霍夫曼编码

3.1具体的作用-频率统计

##实战题目


1.什么是霍夫曼树

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

相关知识:
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。
通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

2.霍夫曼树的构造过程

哈夫曼树的节点个数:
哈夫曼树是一种满二叉树,这意味着除了叶节点外,每个节点都有两个子节点。对于 (N) 个唯哈夫曼树的节点个数与输入的唯一字符数量有关。设字符集合的大小为 (N)(即有 (N) 个唯一字符),则哈夫曼树的节点数最小值和最大值如下:一字符,会有 (N) 个叶节点,每个叶节点代表一个字符。最小值
最小节点数:当输入的字符集合大小为 (N) 时,最小的哈夫曼树(也是理论上的最小值)会有 (2N - 1) 个节点。这是因为在构建哈夫曼树时,每次合并两个节点会创建一个新的内部节点,而最终只剩下一个节点(根节点)没有被合并。这意味着有(N-1) 次合并操作,每次合并操作增加一个新的内部节点,因此有 (N-1) 个内部节点和 (N) 个叶节点,总共 (2N - 1) 个节点。最大值
对于哈夫曼树,最大节点数的概念实际上并不适用,因为哈夫曼树的构建是基于字符的频率的,其目的是为了创建一个尽可能高效的编码方案。每个唯一字符都会成为树的一个叶节点,而内部节点的数量总是 (N-1),所以节点总数固定为 (2N - 1)。不会有比这更多的节点数,因为这是根据哈夫曼算法的定义和工作方式所固有的。结论
对于 (N) 个唯一字符,哈夫曼树的节点数总是 (2N - 1),这里没有最大值概念,因为节点总数是固定的,只要给定了唯一字符的数量。

哈夫曼树的构造过程:

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

3.霍夫曼编码

哈夫曼编码是一种压缩编码的编码算法,是基于哈夫曼树的一种编码方式。哈夫曼树又称为带权路径长度最短的二叉树。

哈夫曼编码跟 ASCII 编码有什么区别?

ASCII 编码是对照ASCII 表进行的编码,每一个字符符号都有对应的编码,其编码长度是固定的。而哈夫曼编码对于不同字符的出现频率其使用的编码是不一样的。其会对频率较高的字符使用较短的编码,频率低的字符使用较高的编码。这样保证总体使用的编码长度会更少,从而实现到了数据压缩的目的。

3.1具体的作用-频率统计

哈夫曼树是一个带权的二叉树,而在哈夫曼编码中,字符的出现频率就是字符的权重。因此要根据字符的频率放入优先队列中进行排序。然后根据这些字符构建一棵哈夫曼树

##代码实现

//c++代码实现
#include <iostream>
#include <vector>
using namespace std;
#include <algorithm> // 包含<algorithm>以使用reverse函数
#define MAXCODELEN 100
#define MAXHAFF 256 // 为了简化,假设最多处理256个不同的字符
#define MAXWEIGHT 10000typedef struct Haffman {//权重 int weight;//字符 char ch;//父节点,左儿子,右儿子 int parent, leftChild, rightChild;
} HaffmanNode;typedef struct Code {int code[MAXCODELEN];int start;
} HaffmanCode;HaffmanNode haffman[MAXHAFF * 2 - 1]; // 扩展数组大小以容纳所有可能的内部节点
HaffmanCode code[MAXHAFF];//构造哈夫曼树的过程 
void buildHaffman(int all) {//对每个节点进行初始化 //哈夫曼节点的个数总为 2 * all - 1 for (int i = 0; i < 2 * all - 1; i++) {haffman[i].weight = 0;haffman[i].parent = -1;haffman[i].leftChild = -1;haffman[i].rightChild = -1;}cout << "请输入需要哈夫曼编码的字符和权重大小" << endl;for (int i = 0; i < all; i++) {//获取每个字符及权重 cout << "请分别输入第" << i + 1 << "个哈夫曼字符和权重:" << endl;cin >> haffman[i].ch >> haffman[i].weight;}//用来存储两个最小权值的节点以及权重 int x1, x2, w1, w2;//all个节点,只需要合并all - 1 次,循环all - 1 次即可 for (int i = 0; i < all - 1; i++) {//初始化成不可能的值 x1 = x2 = -1;w1 = w2 = MAXWEIGHT;//遍历每一个可能的节点,包括初始的叶节点和已经创建的内部节点。 for (int j = 0; j < all + i; j++) {if (haffman[j].parent == -1) {//如果当前节点没有父节点 ,即还没有加入到haffman树中,即考虑这个节点 if (haffman[j].weight < w1) {w2 = w1;x2 = x1;//利用x1,和w1来接受第一个节点和权值 w1 = haffman[j].weight;x1 = j;}//利用x2,和w2来接受第二个节点和权值 else if (haffman[j].weight < w2) {w2 = haffman[j].weight;x2 = j;}}}//循环遍历一遍后,x1,x2,w1,w2即是最小两个节点的选择和权值 //将其加入到huffman树中 haffman[all + i].leftChild = x1;haffman[all + i].rightChild = x2;haffman[all + i].weight = w1 + w2;haffman[x1].parent = all + i;haffman[x2].parent = all + i;}
}void printCode(int all) {//遍历每一个字符-我们所输入的 for (int i = 0; i < all; i++) {//用来存储每一个字符的haffman编码 vector<int> hCode;int current = i;int parent = haffman[current].parent;//通过回溯到根节点,然后确保haffman编码的完整性 while (parent != -1) {//左孩子 '0' if (haffman[parent].leftChild == current) {hCode.push_back(0);}//右孩子 '1' else {hCode.push_back(1);}current = parent;parent = haffman[current].parent;}reverse(hCode.begin(), hCode.end());cout << haffman[i].ch << ": Huffman Code is: ";for (int j = 0 ; j < hCode.size() ; j++){cout << hCode[j] ;}	
//        for (int bit : hCode) {
//            cout << bit;
//        }cout << endl;}
}int main() {cout << "请输入有多少个哈夫曼字符:" << endl;int all = 0;cin >> all;if (all <= 0) {cout << "你输入的个数有误" << endl;return -1;}buildHaffman(all);printCode(all);return 0;
}

#python代码
import heapq
from collections import defaultdict, Counter# 定义树的节点
class Node:def __init__(self, char, freq):self.char = charself.freq = freqself.left = Noneself.right = None# 为了让节点能在优先队列中按频率排序,定义比较方法def __lt__(self, other):return self.freq < other.freq# 构建哈夫曼树
def build_huffman_tree(text):# 统计字符频率frequency = Counter(text)# 创建优先队列priority_queue = [Node(char, freq) for char, freq in frequency.items()]heapq.heapify(priority_queue)# 循环直到队列中只剩一个元素while len(priority_queue) > 1:# 弹出两个最小元素left = heapq.heappop(priority_queue)right = heapq.heappop(priority_queue)# 创建新的内部节点,将两个节点作为子节点merged = Node(None, left.freq + right.freq)merged.left = leftmerged.right = right# 将新节点加入优先队列heapq.heappush(priority_queue, merged)# 队列中剩下的元素就是树的根节点return priority_queue[0]# 生成哈夫曼编码
def generate_codes(node, prefix="", code={}):if node is not None:if node.char is not None:code[node.char] = prefixgenerate_codes(node.left, prefix + "0", code)generate_codes(node.right, prefix + "1", code)return code# 编码文本
def encode(text, codes):return ''.join(codes[char] for char in text)# 解码文本
def decode(encoded_text, root):decoded_text = ""current_node = rootfor bit in encoded_text:if bit == '0':current_node = current_node.leftelse:current_node = current_node.rightif current_node.char is not None:decoded_text += current_node.charcurrent_node = rootreturn decoded_text# 主函数
# def main():
#     text = "this is an example for huffman encoding"
#     root = build_huffman_tree(text)
#     codes = generate_codes(root)
#     encoded_text = encode(text, codes)
#     decoded_text = decode(encoded_text, root)
#
#     print(f"Original text: {text}")
#     print(f"Encoded text: {encoded_text}")
#     print(f"Decoded text: {decoded_text}")# if __name__ == "__main__":
#     main()# 主函数
def main():text = "this is an example for huffman encoding"root = build_huffman_tree(text)codes = generate_codes(root)# 打印每个字符的哈夫曼编码print("Huffman Codes for each character:")for char, code in codes.items():print(f"'{char}': {code}")encoded_text = encode(text, codes)decoded_text = decode(encoded_text, root)print(f"\nOriginal text: {text}")print(f"Encoded text: {encoded_text}")print(f"Decoded text: {decoded_text}")if __name__ == "__main__":main()

##实战题目

3531. 哈夫曼树 - AcWing题库

##示例代码

//c++代码
#include <iostream>
#include <queue>
using namespace std;int n, x, ans;
priority_queue <int, vector <int>, greater <int> > q;int main ()
{cin >> n;for (int i = 1; i <= n; i ++){cin >> x, q.push (x); // 加入二叉堆(优先队列)}while (-- n){x = q.top (), q.pop (), x += q.top (), q.pop (); // 取出两个堆顶(当前最小值)合并ans += x, q.push (x); // 统计答案,在放进堆中}cout << ans;return 0;
}

 148. 合并果子 - AcWing题库

 149. 荷马史诗 - AcWing题库

##代码示例

//c++代码示例
#include<bits/stdc++.h>
using namespace std ;typedef long long LL ;
typedef pair<LL,int> PLI ;
const int N = 1e5 + 10 ;int n , m ;
priority_queue<PLI,vector<PLI>,greater<PLI>> p ;int main()
{cin >> n >> m ;for (int i = 1 ; i <= n ; i++){LL w ;cin >> w ;p.push({w,0}) ;}//这是因为在每次合并操作中,会从堆中取出 m 个节点并合并为一个新节点,这意味着总节点数将减少 m−1。//为了最终能够合并到只剩一个节点,初始的节点数 n 与合并的分支数 m 必须满足 n - 1 是 m - 1 的倍数。while ((n - 1) % (m - 1)){p.push({0ll,0}) ;n++ ;}LL ans = 0 ; while (p.size() > 1){LL sum = 0 ;int depth = 0 ;for (int i = 1 ; i <= m ; i++){sum += p.top().first ;depth = max(depth, p.top().second) ;p.pop() ;}ans += sum ;p.push({sum,depth+1}) ;}cout << ans << endl << p.top().second ;return 0 ;
}

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

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

相关文章

【JavaEE进阶】——MyBatis操作数据库 (#{}与${} 以及 动态SQL)

目录 &#x1f6a9;#{}和${} &#x1f388;#{} 和 ${}区别 &#x1f388;${}使用场景 &#x1f4dd;排序功能 &#x1f4dd;like 查询 &#x1f6a9;数据库连接池 &#x1f388;数据库连接池使⽤ &#x1f6a9;MySQL开发企业规范 &#x1f6a9;动态sql &#x1f388…

能动嘴就别再手动操作了!国产AI大模型让你轻松搞定一切

国产AI大模型使用入门指南 #大模型# ▶▶▶ 引言 简单来说&#xff0c;大模型拥有超级无敌强大的大脑&#xff0c;无所不知&#xff08;不断吸收互联网上海量信息、文献、图书等等它可以找到的数据进行训练&#xff09;。因此&#xff0c;懂得利用AI生成内容&#xff0c;就如同…

DockerCompose中部署Jenkins(Docker Desktop在windows上数据卷映射)

场景 DockerJenkinsGiteeMaven项目配置jdk、maven、gitee等拉取代码并自动构建以及遇到的那些坑&#xff1a; DockerJenkinsGiteeMaven项目配置jdk、maven、gitee等拉取代码并自动构建以及遇到的那些坑_jenkins的安装以及集成jdkgitmaven 提示警告-CSDN博客 Windows10(家庭版…

MySQL的联合索引及案例分析

1. 联合索引 关于联合索引的详解参考博客【Mysql-----联合索引和最左匹配】&#xff0c;包含讲解 最左匹配 联合索引失效的情况 不遵循最左匹配原则范围查询右边失效原理like索引失效原理 比较关注的点在于&#xff1a; 对A、B、C三个字段创建一个联合索引&#xff08;A, …

数据结构之归并排序算法【图文详解】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;LiUEEEEE                        …

FY-SA-20237·8-ZombieFires

Translated from the Scientific American, July/August 2023 issue. Zombie Fires &#xff08;僵尸火灾&#xff09; “Zombie Fires”&#xff08;僵尸火灾&#xff09;是指在地下或地表深处燃烧的火灾&#xff0c;通常在冬季或早春的时候被扑灭&#xff0c;然后在夏季再次…

idea实用快捷键(持续更新...)

文章目录 1、快速输入try/catch/finally2、选中多个光标3、实现接口4、方法参数提示5、查看某个类的子类6、弹出显示查找内容的搜索框 1、快速输入try/catch/finally CtrlAltT 2、选中多个光标 ShiftAlt单机多选 End可以全部到行尾&#xff0c;Home则可以全部回到行首 3、实现接…

Hadoop3:MapReduce源码解读之Mapper阶段的FileInputFormat的切片原理(2)

Job那块的断点代码截图省略&#xff0c;直接进入切片逻辑 参考&#xff1a;Hadoop3&#xff1a;MapReduce源码解读之Mapper阶段的Job任务提交流程&#xff08;1&#xff09; 4、FileInputFormat切片源码解析 切片入口 获取切片 获取切片最大的Size和切片最小的Size 判断文…

可燃气体报警器效检:预防事故,守护家园

在现代化工业生产、居民生活中&#xff0c;可燃气体报警器作为安全预防的重要工具&#xff0c;其准确性和可靠性直接关系到人们的生命财产安全。 因此&#xff0c;对可燃气体报警器进行定期效检&#xff0c;确保其处于最佳工作状态&#xff0c;是保障安全生产的必要措施。 接…

打开C# 大门:Hallo, World!

C# 介绍 C#&#xff08;C Sharp&#xff09;是一种面向对象的编程语言&#xff0c;由微软公司开发。它是 .NET Framework 的一部分&#xff0c;用于构建 Windows 应用程序、Web 应用程序、移动应用程序等。C# 语言的设计目标是简单、现代化、易于学习和使用。在本文中&#xf…

GLM-4已经“低调”开源了

GLM-4-9B 是智谱 AI 推出的最新一代预训练模型 GLM-4 系列中的开源版本。 在语义、数学、推理、代码和知识等多方面的数据集测评中&#xff0c;GLM-4-9B 及其人类偏好对齐的版本 GLM-4-9B-Chat 均表现出较高的性能。 除了能进行多轮对话&#xff0c;GLM-4-9B-Chat 还具备网页浏…

stm32 Systick定时器的配置

从原理上来说&#xff0c;Systick定时器和开发板上的通用定时器没有区别。从功能上来说&#xff0c;Systick定时器主要是用来用来进行延时的&#xff0c;而通用或者高级定时器往往用来进行PWM输出、输入捕获等功能。至于为什么不用通用定时器或者高级定时器来完成延时功能&…

Nginx02-Nginx虚拟主机介绍、日志介绍、Location规则介绍

目录 写在前面NginxNginx处理用户请求流程虚拟主机虚拟主机的分类基于域名的虚拟主机基于端口的虚拟主机基于IP的虚拟主机 Nginx日志错误日志案例 访问日志访问格式变量案例 Location规则案例1案例2Location规则小结 写在前面 这是Nginx第二篇&#xff0c;内容为Nginx处理用户请…

电阻、电容和电感测试仪设计

在现代化生产、学习、实验当中,往往需要对某个元器件的具体参数进行测量,在这之中万用表以其简单易用,功耗低等优点被大多数人所选择使用。然而万用表有一定的局限性,比如:不能够测量电感,而且容量稍大的电容也显得无能为力。所以制作一个简单易用的电抗元器件测量仪是很…

QT之动态加载树节点(QTreeWidget)

之前写过一篇动态加载ComboBox&#xff0c;可参见下面这篇文章 QT之动态加载下拉框&#xff08;QComboBox&#xff09; 同理QTreeWidget也可以实现动态加载&#xff0c;在一些异步加载数据&#xff0c;并且数据加载比较耗时&#xff0c;非常实用。 效果 原理分析 要实现此类效…

【全开源】多功能投票小程序系统源码(ThinkPHP+FastAdmin+Uniapp)

&#x1f680; 多功能投票小程序&#xff0c;让决策变得更简单&#xff01; 基于ThinkPHPFastAdminUniapp开发的多功能系统&#xff0c;支持图文投票、自定义选手报名内容、自定义主题色、礼物功能(高级授权)、弹幕功能(高级授权)、会员发布、支持数据库私有化部署&#xff0c…

PlantUML-使用文本来画时序图

介绍 PlantUML 是一个开源工具&#xff0c;用户可以使用纯文本描述来创建 UML (统一建模语言) 图形。由于它使用文本来描述图形&#xff0c;因此可以很容易地将这些描述与源代码一起存储在版本控制系统中。然后&#xff0c;PlantUML 负责将这些描述转换为图形。 资料 官方文…

工业通讯现场中关于EtherCAT转TCPIP网关的现场应用

在当今工业自动化的浪潮中&#xff0c;EtherCAT技术以其高效、实时的特性成为了众多制造业的首选。然而&#xff0c;随着工业互联网的发展&#xff0c;对于数据的远程访问和云平台集成的需求日益增长&#xff0c;这就需要将EtherCAT协议转化为更为通用的TCP/IP协议。于是开疆智…

基础面试题

目录 MySql 1.连接查询 2.聚合函数 3.SQL 关键字 1.分页 (Iimit) 2.倒序 (order by) 3.分组 (group by) 4.去重 (distinct) 4. SQL Select 语句完整的执行顺序: 5. ★数据库三范式 6. 存储引擎 7.★数据库事务 7.1. ★事务特性: ACID 7.2. ★事务隔离级别 8.★…

《web应用技术》第十次作业

将自己的项目改造为基于vue-cli脚手架的项目&#xff0c;页面有导航&#xff0c;学会使用router。 <el-aside width"200px" style"background-color: aliceblue;"> <el-menu :default-openeds"[1]" style"background-color:rgb(1…