贪婪算法(Huffman编码)

news/2024/4/27 19:05:58/文章来源:https://blog.csdn.net/thdwx/article/details/127532225

如果一个算法分阶段的工作,并且在每一个阶段都认为所做的决定是最好的,而不考虑将来的后果,这样的算法就叫做贪婪算法。贪婪算法只考虑当前局部的最优解,而不去考虑全局的情况,如果最终得到的结果是全局最优的,那么算法就是正确的,否则贪婪算法就只能得到一个次优解。如果不要求得到绝对的答案,那么使用贪婪算法得到次优解也是可以的,否则就需要使用更复杂的算法来得到准确结果。

举一个例子来说明贪婪算法不一定能够得到最优解:如果有12,10,5,1四种面值的纸币,要用最少的纸币来得到16元,如果使用贪婪算法,那么第一张选择12,还需要选择4张1元,这就需要5张纸币,但使用一张10,一张5以及一张1,就只需要三张纸币,这就说明贪婪算法并不总是成功的。

调度问题:

单处理器:

贪婪算法的一个例子是调度问题。现在有作业j_{1},j_{2},j_3,j_4四份作业,完成这四份作业的时间分别为t_1,t_2,t_3,t_4,只有一个处理器,并且使用非预占调度:一旦开始一个作业,就必须把当前的作业运行完。如果要使完成作业的平均时间最短,应该如何调度?

作业时间
j_115
j_28
j_33
j_410

k表示处理的顺序,i_k表示作业的编号,那么处理完这四份作业所需要的总时间(即每个作业从处理器开始工作到完成该作业所花费的时间总和):

T=

T=

在第二个方程中,可以看到,等式后面的第一项是与作业处理顺序无关的,而要使T最小,第二项就应该尽可能的大 ,那么由数学中的排序不等式可知,当kt_{i_k}为同序时,也就是花费时间少的作业,处理的顺序也应该靠前,这样就能使第二项达到最大,从而使总时间达到最小。所以最优调度方案如图:

多处理器:

将问题推广到多处理器,假设有9份作业和3个处理器,那么最优调度方案其实与单处理器相似,只需要将9份作业从小到大排序,依次交由三个处理器处理即可(即使作业的份数不能整除处理器的个数,最优解依然是存在的):

工作时间
j_13
j_25
j_36
j_410
j_511
j_614
j_715
j_818
j_920

但多个处理器在平均时间最优的情况下会有一个最终结束时间问题,如果交换7,8,9三份作业的位置,如下图:

那么可以看到,第二个方案的最终结束时间是38,而第一个方案是40。文中的这个例子存在一个最优的结束时间:

虽然对于单个处理器来说,完成自身所有任务的平均时间可能并没有前两个方案好,但是如果在实际中,我们需要等待这些任务处理完成后,再去进行下一步的操作,那么这时候最短的结束时间就是很有必要的。

Huffman编码:

贪婪算法的另一个例子就是文件压缩问题。

对于一个需要传输的文件,如果将文件中的所有字符都以ASCII码值编码,那么每个字符就需要8个bit,在文件比较大并且带宽较小的情况下,这个文件的传输将会非常的耗时。实际上,ASCII码值占8个bit是由于:标准的ASCII字符集由100个可打印字符及一些非打印字符组成,1个bit能表示两种情况,那么7个bit就能够表示128种不同的字符,再加上最高位作为奇偶校验位(校验数据传输是否正确),总共8个bit。

那么,如果一个文件中出现的不同字符总共有N个,实际上每个字符就需要\left \lceil log_{2}N \right \rceil个比特位来表示,在这种编码下,文件就被缩小了。

假设有一个文件,包含字符a、e、i、s、t,再加上一些空格和换行,并且有10个a、15个e、12个i、3个s、4个t、13个空格和1个换行,如图所示,这个文件就需要174位来表示:

字符二进制编码频率总位数
a0001030
e0011545
i0101236
s01139
t100412
空格1011339
换行11013
总和174

这个编码值可以使用一棵二叉树来表示:

在树中,左边表示0,右边表示1,每一个叶节点都代表一个字符,这样就可以保证编码无冲突,从根节点找到一个叶节点所经过的路径,就代表了这个叶节点字符的编码值。观察这幅图可以发现,换行符的父节点只有它一个孩子,所以可以将换行符放到它父节点的位置:

此时,换行符的编码就变成了11,则文件的总位数也变成了173,节省了一个bit的长度。上图中的树称为满树:所有的节点要么是叶子节点,要么有两个子节点。一些最优的编码总是具有这个性质,否则根据刚才的操作,我们总可以将只有一个孩子的节点向上移动一层 。

如果字符只放在叶子节点上,那么所有的字符就总是能被无歧义的编码。否则,假设o的编码是00,那么o的位置如图:

对于二进制码000001,我们就不能够知道它代表的是 ae 还是 oo或是其他,这样在编码时就会产生歧义,因为o是a的前缀编码。所以只有字符都放在叶子节点上,那么它们就不可能是任何其他字符的前缀编码,自然也就不会产生歧义。在文件中,e出现了最多次,那么如果我们将nl与e的位置互换,最后文件就只有159位,这就是Huffman算法的思想。

Huffman算法:

由Huffman算法生成的树就叫作Huffman树,得到的编码就是Huffman编码。Huffman算法的基本思想就是:对于一个文件中出现的所有字符,首先将其建立为一个森林,每个节点都存放字符本身以及字符出现的频率,然后选择其中频率最小的两个节点合并为一棵树,这棵树的频率为合并的两个节点频率树之和,然后将合并后的树放回森林,再找到两个频率最小的节点重复上述工作,直到最后只剩一棵树,就完成了Huffman编码。对于上述文件,建立Huffman树的具体过程如下:

首先建立初始森林:

选择频率最小的两个节点nl、s进行合并:

 然后重复操作:

 

 

 

 

到这里就完成了Huffman树的建立。

Huffman树一定是一棵满树,因为如果不是满树,就一定存在一个只有一个孩子的节点,那么在合并时,就一定是只将森林中的一个节点与一个NULL进行合并,这与我们的操作过程是不符的;频率最小的节点一定是最深的,由数学归纳法知,在第一次合并时,显然结论是成立的。假设在T3次合并时结论也成立,那么在T5次合并时,如果e的频率小于T3的频率 ,并且e的频率要小于a或是T2的频率,那么在T3合并时,就会先将e进行合并,所以e的频率一定是大于比它更深的节点的。这样就可以使出现次数最多的字符的编码值是最短的,得到的编码就是最优的。

Huffman算法是一个贪婪算法是因为,在每次合并时,都是选择当前频率最小的两个节点进行合并,而并没有进行全局的考虑。如果使用堆来找到节点中频率最小的两个节点,那么Huffman算法的时间复杂度为O(NlogN)N为不同字符的个数。如果使用插入排序的话,时间复杂度就为O(N^2)。将N个节点合并成一棵树需要线性时间,所以时间主要花费在排序上。

有两个细节需要考虑:

一是在传输压缩文件时,必须要传送编码信息,否则将无法解码,如果文件本身并不大,那么传输编码信息的代价可能超过了编码所带来的节省,这个时候不对文件进行压缩可能是一个更好的选择。

二是我们首先要直到文件中每个字符的频率,然后才能对它们使用Huffman算法,如果文件过大,那么就需要慎重考虑得到字符频率的算法。

Huffman算法代码如下:

typedef struct TreeNode {//树的节点,存放字符、频率以及子节点信息char ch;int times;struct TreeNode* nodes[2];//向左为0,向右为1,正好以数组下标来表示
}TreeNode;typedef struct HuffmanTree {//Huffman树,存放树的根节点TreeNode* root;
}HuffmanTree;void insertion_sort(TreeNode** p, int n) {//插入排序for (int i = 1; i < n; i++) {TreeNode* tmp = p[i];int j;for (j = i; j >= 1 && p[j - 1]->times < tmp->times; j--) {p[j] = p[j - 1];}p[j] = tmp;}
}HuffmanTree* BuildHuffmanTree(TreeNode* arr, int n) {//建立一个Huffman树HuffmanTree* p = (HuffmanTree*)malloc(sizeof(HuffmanTree));p->root = NULL;TreeNode** pt = (TreeNode**)malloc(sizeof(TreeNode*) * n);//建立一个森林for (int i = 0; i < n; i++) {//将每个字符的数据都建立成一个树节点TreeNode* ptt = (TreeNode*)malloc(sizeof(TreeNode));ptt->ch = arr[i].ch;ptt->nodes[0] = ptt->nodes[1] = NULL;ptt->times = arr[i].times;pt[i] = ptt;}insertion_sort(pt, n);//对森林进行排序,从大到小for (int i = n - 1; i >= 1; i--) {TreeNode* ptt = (TreeNode*)malloc(sizeof(TreeNode));ptt->nodes[0] = pt[i];//选择最小的两个节点,并将它们合并ptt->nodes[1] = pt[i-1];ptt->times = pt[i]->times + pt[i - 1]->times;pt[i - 1] = ptt;//每合并两个节点,森林中的节点数就会-1,所以将合并后的节点放在pt[i-1]位置,并重新排序insertion_sort(pt, i);}p->root = pt[0];//当最后合并的节点放在pt[0]处时,说明森林中只剩一棵树,那么这个节点就是Huffman树的根节点return p;//返回根节点
}void print(TreeNode* p, char* buff, int i) {if (p->nodes[0] == NULL && p->nodes[1] == NULL) {//如果左右子树都为空,说明这个就是字符所在的叶节点buff[i] = 0;//那么buff就是该字符的编码值,所以直接输出printf("%c   %s\n", p->ch, buff);return;}buff[i] = '0';//否则,向左走为0,向右走为1,深度也+1print(p->nodes[0], buff, i + 1);buff[i] = '1';print(p->nodes[1], buff, i + 1);
}void Print(HuffmanTree* tree) {//打印所有字符的编码值char* buff = (char*)calloc(10, 1);//使用一个字符数组来存放当前层的前缀if (tree->root == NULL) {//根节点为空说明没有编码printf("Huffman树为空\n");return;}int i = 0;//用来记录层数print(tree->root, buff, i);
}

测试代码:

void test() {//输入10个小写字母以及它们的频次#define NUM 10TreeNode arr[NUM] = { 0 };//用来存放字符信息,以便生成森林char str[10] = { 0 };for (int i = 0; i < NUM; i++) {scanf("%s %d", str, &arr[i].times);//直接以字符串形式读入,就不必再考虑空格换行符等情况arr[i].ch = str[0];}HuffmanTree* p = BuildHuffmanTree(arr, NUM);Print(p);return;
}

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

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

相关文章

【pytest官方文档】解读- 开发可pip安装的第三方插件

在上一篇的 hooks 函数分享中,开发了一个本地插件示例,其实已经算是在编写插件了。今天继续跟着官方文档学习更多知识点。 一个插件包含一个或多个钩子函数,pytest 正是通过调用各种钩子组成的插件,实现了配置、搜集、运行和报告的所有方面的功能。 通常 pytes t中的插件有…

OS实战笔记(7)-- Linux同步机制

上一篇笔记中对x86平台上原子变量、关中断、自旋锁和信号量的原理做了复习&#xff0c;本笔记回顾一下Linux使用的几种常用的同步机制。 Linux上的原子变量 Linux上提供了一个atomic_t类型表示原子变量。32位和64位版本的结构体定义如下&#xff1a; typedef struct {int coun…

文件描述符0,1,2+lseek()+共享文件覆盖解决

1.文件描述符0&#xff0c;1&#xff0c;2 程序开始运行时&#xff0c;有三个文件被自动打开&#xff0c;打开时分别使用了这三个文件描述符依次打开的三个文件分别是 /dev/stdin&#xff0c;/dev/stdout&#xff0c;/dev/stderr /dev/stdin 标准输入文件 程序开始运行时&…

requests模块和openpyxl模块

第三方模块的下载和使用 1,第三方模块就是别人大神们已经写好的模块,功能特别强大。我们如果像使用第三方模块就先要进行下载。下载完成后 才可以在python中直接调用2.下载方式一:pip工具pip工具注意每个解释器都有pip工具 如果我们的电脑上有多个版本的解释器那么我们在使用…

模型交易平台--信用卡客户消费行为分析

业务问题&#xff1a; 据《中国银行卡产业发展蓝皮书&#xff08;2021&#xff09;》显示&#xff0c;截至2020年末&#xff0c;中国信用卡累计发卡量为11.3亿张&#xff0c;其中6个月内有使用记录的活卡为7.4亿张&#xff0c;有近4亿张信用卡处于“睡眠”状态。 睡眠信用卡…

【JUC】12.读写锁与StampedLock[完结]

文章目录1. 什么是读写锁2. 锁的演化历程3. 锁降级4. 锁降级的策略5. StampedLock简介6. StampedLock的特点7. StampedLock之传统读写8. StampedLock之乐观锁9. StampedLock缺点1. 什么是读写锁 读写锁是指ReentrantReadWriteLock类 该类能被多个读线程访问或者一个写线程访问…

第二节:数据类型与变量【java】

目录 &#x1f4c3;前言 &#x1f4d7;1.数据类型 &#x1f4d5;2. 变量 2.1 变量概念 2.2 语法格式 &#x1f4d9;3.整型变量 3.1 整型变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 &#x1f4d8;4.浮点型变量 4.1 双精度浮点型 4.2 单精度浮点型 &#…

拓端tecdat|用Python进行图像模糊处理和特征提取

全文链接&#xff1a;http://tecdat.cn/?p9015 原文出处&#xff1a;拓端数据部落公众号 在本文中&#xff0c;我将带您了解图像处理的一些基本功能。特征提取。但是这里我们需要更深入的数据清理。但是数据清理是在数据集&#xff0c;表格&#xff0c;文本等上完成的。如何在…

vue使用canvas实现手写电子签名;vue使用vue-esign插件实现手写电子签名;H5使用画布签名

功能&#xff1a; 1.兼容 PC 和 Mobile&#xff1b; 2.画布自适应屏幕大小变化&#xff08;窗口缩放、屏幕旋转时画布无需重置&#xff0c;自动校正坐标偏移&#xff09;&#xff1b; 3.自定义画布尺寸&#xff08;导出图尺寸&#xff09;&#xff0c;画笔粗细、颜色&#xff0…

Arduino UNO 可视化GT-24工业级无线透传

Arduino UNO 可视化GT-24工业级无线透传一、前言二、硬件要求三、参数基础四、原理剖析五、透传思路六、程序概要七、arduino使用接线八、成果展示一、前言 无线透传市面上较为常见的是基于蓝牙、esp的多种透传模块&#xff0c;今天介绍的则是用NRF24L01芯片构成的电路。&…

Python第七章作业

实例一: class Geese: 大雁类 def __init__(self,beak,wing,claw): print("我是大雁类!我有以下特征:") print(beak) print(wing) print(claw) def fly(self,state): print(state)**********调用方法**********beak_…

MyBatis中的reflection包(一)ObjectFactory,PropertyTokenizer, Invoker, Reflector

内容概要 reflection是MyBatis关于反射的工具包&#xff0c;是实现其它功能的基石之一。这里我不准备贴上源码然而逐行解释&#xff0c;而是从需求分析的角度来复现。 ObjectFactory 现在有这样的需求&#xff1a;给你一个Class对象&#xff0c;要求你创建它的实例&#xff…

Kong自动注册kong-spring-boot-stater

前言 kong-spring-boot-stater框架是为了解决SpringBoot项目和kong网关的自动注册&#xff0c;虽然Kong网关有提供可视化管理后台的操作界面&#xff0c;但是在多服务、多环境的时候在管理后台挨个配置每个服务节点是比较麻烦的&#xff0c;所以这也是kong-spring-boot-stater…

图形写稿基础,含teaser figure的特殊排版方法

写在前面&#xff1a;这是第一次投稿后针对论文写作部分的总结。需要注意的是&#xff1a;老师提了意见&#xff0c;一定要快速改&#xff0c;否则会很恼人。 1. 图片展示 构图要美观&#xff0c;保证横平竖直&#xff1b;图片中文字保证和文章正文中文字一样大小&#xff1b;…

VUE |“ 登录页面”的权限以及接口问题

目录 一、功能需求 二、前提准备 三、具体实现 一、功能需求 今天写到项目的登录页面&#xff0c;我这边是没有后台数据接口的&#xff0c;所以我们用了Mock模拟了一个假的数据&#xff0c;那么我们应该怎么实现呢&#xff1f;我们先来看一下功能需要。 当我们退出登录…

系分 - 系统可靠性分析与设计

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 系统可靠性分析与设计 考点摘要 可靠性相关基本概念&#xff08;★&#xff09;系统可靠性分析&#xff08;★★&#xff09;软件可靠性设计&#xff08;★★&#xff09; 系统故障类型 系统故障是指由…

09代码

实例1 def division():print(\n==========分苹果了===========\n)apple=int(input(请输入苹果的个数))children=int(input(请输入来了多少个小朋友))result=apple//childrenremain=apple-result*childrenif remain>0:print(apple,个苹果,平均分给,children,个小朋友,每人分…

网络爬虫及openyxl模块

网络爬虫及openyxl模块 一、第三方模块简介 1.第三方模块的用处python之所以在这么多的编程语言中脱颖而出的优点是有众多的第三方库函数,可以更高效率的实现开发2.第三方模块的使用 1.第三方模块必须下载才能使用格式:pip install 模块名 -i 源地址清华大学 :https://pypi.…

【cuda编程】CUDA的运行方式以及grid、block结构关系

文章目录1. CUDA基础知识1.1 程序基本运行顺序1.2 grid与block1.3 dim类型定义2. CUDA的第一个程序3. CUDA线程的组织结构——grid与block关系1. CUDA基础知识 1.1 程序基本运行顺序 一般来说&#xff0c;一个cpugpu的程序运行如下所示&#xff1a; 1.2 grid与block 从GPU至…

网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理

JavaEE传送门JavaEE 网络原理——No.2 传输层_TCP的连接管理 网络原理——No.3 传输层_TCP的滑动窗口, 流量控制与拥塞控制 目录延迟应答捎带应答面向字节流粘包问题TCP 中的异常处理(连接异常)TCP 和 UDP 的应用场景延迟应答 一种提高传输效率的机制, 又是基于流量控制, 来引…