计算机算法分析与设计(12)---贪心算法(最优装载问题和哈夫曼编码问题)

news2024/10/12 9:25:45

文章目录

  • 一、最优装载问题
    • 1.1 问题表述
    • 1.2 代码编写
  • 二、哈夫曼编码
    • 2.1 哈夫曼编码概述
    • 2.2 前缀码
    • 2.3 问题描述
    • 2.4 代码思路
    • 2.5 代码编写


一、最优装载问题

1.1 问题表述

 1. 有一批集装箱要装上一艘载重量为 c c c 的轮船,已知集装箱 i ( 1 ≤ i ≤ n ) i(1≤i≤n) i(1in) 的重量为 w i w_i wi。最优载问题要求在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。

 2. 贪心选择策略:重量最轻者优先装载

 3. 算法思路:将装船过程划分为多步选择,每步装 1 1 1 个货箱,每次从剩下的货箱中选择重量最轻的货箱。如此下去直到所有货箱均装上船或船上不能再容纳其他任何一个货箱。

1.2 代码编写

算法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

#include<algorithm>
#include<iostream> 
using namespace std;

int main(){
    int n; //定义货箱个数
    int c; //定义船的最大承载量
    cout<<"输入货箱个数以及船的最大承载量"<<endl;
    cin>>n>>c;
    
    cout<<"输入每件货物的重量"<<endl;
    int w[n]; //用数组填装货箱的重量
    for(int i=0;i<n;i++)
	{
        cin>>w[i];
    }

    sort(w,w+n); //快排将货箱的重量由小到大进行排序
    
    int temp=0; //中间值
    int count=0; //计数器
    
    for(int i=0;i<n;i++)
	{
        temp = temp + w[i];
        if(temp<=c)
		{
            count++;

        }
        else
		{
            break;
        }
    }

    cout<<"能装入货箱的最大数量为"<<count<<endl;
    return 0;
}

二、哈夫曼编码

2.1 哈夫曼编码概述

 1. 哈夫曼编码是在电讯通信中的应用之一,广泛地用于数据文件压缩的十分有效的编码方法,其压缩率通常在20%~90%之间。在电讯通信业务中,通常用二进制编码来表示字母或其他字符,并用这样的编码来表示字符序列。

 2. 例如:如果需传送的电文为 ‘ A B A C C D A ’ ‘ABACCDA’ ABACCDA,它只用到四种字符,用两位二进制编码便可分辨。假设 A , B , C , D A, B, C, D A,B,C,D 的编码分别为 00 , 01 , 10 , 11 00, 01,10, 11 00,01,10,11,则上述电文便为 ‘ 00010010101100 ’ ‘00010010101100’ ‘00010010101100’(共 14 位),译码员按两位进行分组译码,便可恢复原来的电文。

2.2 前缀码

 1. 前缀码定义:对每一个字符规定一个 0 , 1 0,1 0,1 串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀,这种编码称为前缀码。

abcdef
编码方式1010110011111011100
编码方式20100011011

 很容易发现,当我们在用编码方式 2 2 2 时,解码会出现问题,例如 00110 00110 00110 既可以解码成 a a b b a aabba aabba,也可以解码成 c f a cfa cfa,解码结果不唯一,编码方式不可行。
 而用前缀码(即编码的任意前缀不是其他编码),则解码结果唯一,编码方式可行。

2.3 问题描述

 1. 如果要求得到最优的编码方式,那不同的前缀码如何比较它们的优劣呢?我们可以通过比较编码后二进制串的总长,总长越短,说明这种编码方式越优。而编码后二进制的总长,依赖于待编码字符的频数。假设我们给出的带编码字符及其频数和两种不同的前缀码,如下表所示。

abcdef
频数(千次)4513121695
前缀码1010110011111011100
前缀码2110011011111001010

 通过计算可知,使用前缀码 1 1 1编码后二进制串的总长是 224000 224000 224000,使用前缀码 2 2 2编码后二进制串的总长是 348000 348000 348000,显然,前缀码 1 1 1要优于前缀码 2 2 2

 2. 贪心策略:出现频率较高的字符的编码较短出现频率较低的字符的编码较长。使用二叉树对其进行编码和解码。
在这里插入图片描述

2.4 代码思路

 1. 给定编码字符集 C C C C C C 中的任一字符 c c c 的出现频率 f ( c ) f(c) f(c) C C C 的一个前缀码编码方案对应一棵二叉树 T T T。字符 c c c 在树 T T T 中的深度记为 d T ( c ) d_T(c) dT(c)。定义该编码方式的平均码长为:

在这里插入图片描述

在这里插入图片描述
 2. 哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树 T T T。算法以 n n n 个叶结点开始,执行 n − 1 n-1 n1 次合并,运算后产生最终所要求的树 T T T。在哈夫曼树中,编码字符集中每个字符 c c c的频率是 f ( c ) f(c) f(c)。以 f f f 为键值的优先队列 Q Q Q 在用做贪心选择时有效地确定当前要合并的两棵具有最小频率的树。一旦两棵具有最小频率的树合并后,产生一棵新的树,其频率和为合并的两棵树的频率之和,并将新树加入 Q Q Q

2.5 代码编写

算法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

#include<iostream>
using namespace std;

#define MaxNode 200
#define MaxBit 100

//节点,一般遵循左0右1 
typedef struct
{
	double weight; //权重
	int parent; //父节点
	int lchild; //左孩子节点
	int rchild; //右孩子节点
	char value; //节点代表的字符
}hufnode;

typedef struct
{
	int start; //开始指针,记录每个字母编码在数组里开始的位置
	int bit[10]; //存放赫夫曼编码
}hufbit;

hufnode hujd[MaxNode];
hufbit hb[MaxBit];

//初始化节点,parent,lchild,rchild=-1,weight=0
void initNode()
{
	for (int i = 0; i < MaxNode; i++)
	{
		hujd[i].lchild = -1;
		hujd[i].parent = -1;
		hujd[i].rchild = -1;
		hujd[i].weight = 0;
	}
}

//建立哈弗曼树
void creathuf(int n)
{
	int i;
	cout << "请输入每个节点的字符和权值" << endl;
	for (i = 0; i < n; i++)
	{
		cin >> hujd[i].value >> hujd[i].weight;
	}
 	
    //定义两个变量m1和m2用来存储最小的两个未处理的权值,以及两个变量x1和x2用来存储对应的最小权值的节点索引。
	double m1, m2;
	int x1, x2;
	for (int i = 0; i < n - 1; i++)
	{
		m1 = m2 = 1000;
		x1 = x2 = 0;
		
		for (int j = 0; j < n + i; j++)
		{
			//找最小权值结点 
			if (hujd[j].weight < m1 && hujd[j].parent == -1)
			{
				m2 = m1;
				x2 = x1;
				m1 = hujd[j].weight;
				x1 = j;
			}
			//找第二最小权值结点 
			else if (hujd[j].weight < m2 && hujd[j].parent == -1)
			{
				m2 = hujd[j].weight;
				x2 = j;
			}
		}
		
        //创建一个新的父节点,其左孩子为x1,右孩子为x2		
		hujd[n + i].weight = m1 + m2;
		hujd[n + i].lchild = x1;
		hujd[n + i].rchild = x2;
		hujd[x1].parent = n + i;
		hujd[x2].parent = n + i;
	}
}

//编码
void tshuff(int n)
{
	//p用于保存当前节点的父节点的索引,c用于保存当前节点的索引。
	int p, c;
	
	for (int i = 0; i < n; i++)
	{
		p = hujd[i].parent;  //获取当前节点的父节点索引并赋值给p
		c = i;               //获取当前节点的索引并赋值给c
		hb[i].start = n - 1; //将当前节点的起始位设置为n-1,这可能意味着我们是从最右边开始编码的
		while (p != -1)      //当前节点不是根节点时进行循环
		{
            //如果当前节点是父节点的左孩子,我们将起始位处的二进制位设为0,并将起始位右移一位(向树的左边移动)
			if (hujd[p].lchild == c)
			{
				hb[i].bit[hb[i].start] = 0;
				hb[i].start--;
			}
			//如果当前节点是父节点的右孩子,我们将起始位处的二进制位设为1,并将起始位右移一位(向树的左边移动
			if (hujd[p].rchild == c)
			{
				hb[i].bit[hb[i].start] = 1;
				hb[i].start--;
			}
			//将当前节点的索引赋值给p,将父节点的索引赋值给c,然后开始下一轮循环。
			//这个过程一直持续到p等于-1为止,也就是说,当我们到达树的根节点时,循环就会结束。
			c = p;
			p = hujd[p].parent;
		}
	}
}

void output(int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << hujd[i].value << ":";
		for (int j = hb[i].start+1; j < n; j++)
		{
			cout << hb[i].bit[j];
		}
		cout << endl;
	}
}
int main()
{
	int n;
	cout << "请输入有几个字符:" << endl;
	cin >> n;
	initNode();
	creathuf(n);
	tshuff(n);
	cout << "编码方式为:" << endl; 
	output(n);
	return 0;	
}

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

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

相关文章

神经网络中的反向传播:综合指南

塔曼纳 一、说明 反向传播是人工神经网络 &#xff08;ANN&#xff09; 中用于训练深度学习模型的流行算法。它是一种监督学习技术&#xff0c;用于调整网络中神经元的权重&#xff0c;以最小化预测输出和实际输出之间的误差。 在神经网络中&#xff0c;反向传播是计算损失函数…

纳米软件科普|半导体常见的测试种类详解

纳米软件科普下半导体几种常见的测试方法种类的详细介绍&#xff0c;有你不了解的吗&#xff1f; 外观检测&#xff1a;这一步骤主要是对半导体外观质量的评估。它包括检查芯片的平整度、颜色、镜面度等&#xff0c;以确保半导体表面无明显缺陷或不规则形状。 电性能测试&…

Kafka 使用java实现,快速入门

一、kafka的生产者和消费者 1. 生产者发送消息的流程 2. 消费者接收消息的流程 二、 java 代码实现 1. 添加依赖&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.12</artifactId></dependency>2. 实现…

vue3+koa+axios实现前后端通信

vue3koaaxios实现前后端通信 写了一个小demo来实现前后端通信,涉及跨域问题&#xff0c;非常简单可以给大家平时开发的时候参考 服务端&#xff1a; 目录结构如下&#xff1a; router index.js // router的入口文件 // 引入路由 const Router require("koa-router&quo…

React组件渲染和更新的过程

一、回顾Vue组件渲染和更新的过程 二、回顾JSX本质和vdom 三、组件渲染和更新 1、组件渲染过程 props state (组件有了props state)render()生成vnodepatch(elem, vnode) 2、组件更新过程 setState(newState) --> dirtyComponents (可能有子组件)render()生成newVnodepa…

3、Flowable任务分配和流程变量

任务分配和流程变量 1.任务分配 1.1 固定分配 固定分配就是我们前面介绍的&#xff0c;在绘制流程图或者直接在流程文件中通过Assignee来指定的方式 1.2 表达式分配 Flowable使用UEL进行表达式解析。UEL代表Unified Expression Language&#xff0c;是EE6规范的一部分.Flo…

通配符SSL证书价格贵吗

通配符SSL证书是一种SSL证书&#xff0c;用于保护基于相同域名但具有不同子域名的多个网站。 它使用通配符字符 "*" 来代表所有可能的二级子域名。JoySSL注册码230609领取免费使用通配符证书 通配符SSL证书的作用是使您可以使用单个证书来保护主域名和所有其下的子域…

千兆光模块和万兆光模块的区别?

在网络通信领域&#xff0c;千兆光模块和万兆光模块是最为常见且广泛应用的两种光模块。不同之处在于传输速率、封装、传输距离、功耗、发射光功率、接收光功率和应用场景等。 千兆光模块的传输速率为1 Gbps&#xff0c;万兆光模块的传输速率为10 Gbps&#xff0c;这意味着万…

柯桥基础俄语口语|初级俄语学习:用联想法学习字母

初级俄语 用联想法学习字母 开始学习俄语的时候&#xff0c;第一任务就是掌握字母表。今天彼得俄语会帮助大家巧记俄语字母表。为此&#xff0c;小编把所有字母分成三组&#xff1a;与拼音相似的&#xff0c;可以用图片记住的以及可以用汉字记住的。 与拼音相似的字母 俄语有3…

数据结构与算法-(9)---双端队列(Deque)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

TCP通信-同时接受多个客户端消息

同时处理多个客户端消息的原理 代码实现 public class ClientDemo1 {public static void main(String[] args) {try {System.out.println("客户端启动");// 1、创建Socket通信管道请求有服务端的连接// public Socket(String host, int port)// 参数一&#xff1a;服…

图像处理软件Photoshop 2023 mac新增功能 ps 2023中文版

​Photoshop 2023 mac是一款功能强大、易用且灵活的图像编辑软件&#xff0c;旨在满足专业设计师和摄影师的需求。无论您是处理照片、制作图形还是进行艺术创作&#xff0c;Photoshop 2023 都能为您提供丰富的工具和效果&#xff0c;帮助您实现创意想法。Photoshop还支持多种文…

无蓝光的护眼灯有哪些品牌?分享五款优秀的无蓝光护眼台灯

现在儿童近视率越来越高了&#xff0c;用眼过度疲劳是导致近视的主要因素&#xff0c;学习环境的光线是否合适&#xff0c;都会直接影响用眼的疲劳程度。所以给孩子营造一个良好的学习环境非常重要&#xff01;为大家推荐五大品牌的护眼台灯。 1.书客护眼台灯L1 推荐指数&…

如何压缩ppt文件的大小?

如何压缩ppt文件的大小&#xff1f;要知道现在很多课件都是使用ppt文件&#xff0c;那么就导致ppt文件过大&#xff0c;我们很多时候电脑的存储空间就不够了。为了能够更好的存储这些ppt文件&#xff0c;我们通常会选择压缩ppt文件。怎么压缩ppt文件更快更好&#xff0c;没有损…

[java进阶]——IO流,递归实现多级文件拷贝

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、认识IO流 二、了解编码与解码 二、IO流体系 三、字节输入输出流 四、字符输入输出流 五、多级文件拷贝 一、认识IO流 IO流也叫输入流(intput)、输出流(onput)&#xff0c;该流就像java程序同硬盘之间的…

迅为RK3588开发板Android12单摄方案设备树修改

打开 3588-android12/kernel-5.10/arch/arm64/boot/dts/rockchip/topeet_camera_config.dtsi 设备树&#xff0c;此设备树中对底板上的摄像头接口进行了配置&#xff0c;如下图所示&#xff1a; 如果想要使用 J1 接口打开摄像头 OV5695 或者 摄像头 OV13850&#xff0c;只需要在…

django建站过程(2)创建第一个应用程序页面

创建第一个应用程序页面 设置第一个页面【settings.py,urls.py,views.py】settings.pyurls.pyviews.py django是由一系列应用程序组成&#xff0c;协同工作&#xff0c;让项目成为一个整体。前面已创建了一个应用程序baseapp,使用的命令 python manage.py startapp baseapps这…

QT最小化到托盘显示

一、效果&#xff1a; 程序关闭后&#xff0c;程序并没有退出&#xff0c;而是放入了托盘中&#xff1b;点击恢复原始大小&#xff0c;或者双击托盘图标&#xff0c;可以恢复程序原来的窗口。如下图。 那qt是如何实现这样的办法呢&#xff0c;其实就是用到了 QSystemTrayIcon类…

Java开发树结构数据封装!

目录 源数据如下controller接口&#xff1a;service层封装:Dao接口&#xff1a;Dao层Mapper:映射实体类&#xff1a; 源数据如下 controller接口&#xff1a; RequestMapping("/UserTreeInfo")public RespBody getUserTreeInfo(Long userId) {List<MenuTreeVo>…

如何使用BERT生成单词嵌入?

阿比贾特萨拉里 一、说明 BERT&#xff0c;或来自变形金刚&#xff08;Transformer&#xff09;的双向编码器表示&#xff0c;是由谷歌开发的强大语言模型。它已广泛用于自然语言处理任务&#xff0c;例如情感分析、文本分类和命名实体识别。BERT的主要特征之一是它能够生成单词…