C++ 算法主题系列之贪心算法的贪心之术

news2024/11/28 0:38:28

1. 前言

贪心算法是一种常见算法。是以人性之念的算法,面对众多选择时,总是趋利而行。

因贪心算法以眼前利益为先,故总能保证当前的选择是最好的,但无法时时保证最终的选择是最好的。当然,在局部利益最大化的同时,也可能会带来最终利益的最大化。

假如在你面前有 3 个盘子,分别放了很多苹果、橙子、梨子。

现以贪心之名,从 3 个盘子里分别拿出其中最重的那个,则能求解出不同品种间最重水果的最大重量之和。这时,3 个盘子可认为是 3 个子问题(局部问题)。

贪心算法会有一个排序过程。当看到盘子里的水果时,潜意识中你就在给它们排序。有时,因可排序的属性较多,必然导致使用贪心算法求解问题时,贪心策略可能会有多种。

比如说选择水果,你可以选择每盘里最大的、最重的、最成熟的……具体应用那个策略,要根据求解的问题要求而定。贪心策略不同,导致的结论也会不一样。

本文将通过几个案例深入探讨贪心算法的贪心策略。

2. 活动安排问题

问题描述:

  • n个活动a1,a2,…,an……需要在同一天使用同一个教室,且教室同一时刻只能由一个活动使用。
  • 每个活动ai都有一个开始时间si和结束时间fi
  • 一个活动被选择后,另一个活动则需等待前一个活动结束后方可进入教室。
  • 请问,如何安排这些活动才能使得尽量多的活动能不冲突的举行。

分析问题:

教室同一时间点只能安排一个活动,如果活动与活动之间存在时间交集(重叠),则这两个活动肯定是不能在同一天安排。

使用 S表示活动的开始时间,f表示活动的结束时间。则可使用 [si,fi]表示活动一,[sj,fj]表示活动二。

如果存在:si<sj<fi<fj,则称这 2 个活动不相容,不相容的活动不能在一天内同时出现。如下图所示。

1.png

只有当活动二的开始时间等于或晚于上活动一的结束时间时方可以在同一天出现。如下图所示。也即sj>=fi。此时,称这 2 个活动是相容。

2.png

此问题中,活动的属性有:

  • 活动的开始时间。
  • 活动的结束时间。
  • 活动的时长。

那么,在使用贪心算法时,贪心的策略应该如何定夺?

2.1 贪心策略

2.1.1 策略一

最直接的想法是:每次安排活动时长最短的活动,必然可保证一天内容纳的活动数最多。

逻辑层面上讲,先以活动时长递增排序,然后依次选择。

但是忽视了一个问题,每一个活动都有固定的开始时间和结束时间。

如下图所示,因活动一所需时长最短,先选择。又因活动二所需时长次短,选择活动二。因两个活动时间间隔较长,采用此策略,会导致教室出现空闲时间。

3.png

针对此问题,贪心算法的出发点应该是保证每一时刻教室的最大利用率,才有可能容纳最多活动。

显然,受限于活动固定时间限制,此策略不可行。

2.1.2 策略二

能否每次选择活动时长最长的活动。提出这个策略时,就不是很自信的。分析问题吗?就不论正确,只论角度。

此策略和策略一相悖论。题目要求尽可能安排活动的数量,如果某活动的时长很长,一个活动就占据了大部分时间,显然,此种策略是无法实现题目要求的。

如下图,选择活动一,后再选择活动三。一天仅能安排 2 次活动。根据图示,根据每个活动的开始、结束时间,一天之内至少可以安排 3 个活动。

4.png

2.1.3 策略三

每次选择最早开始的活动。

先选择最早开始,等活动结束后,再选择相容且次开始的。这个策略和策略二会有同样的问题。如果最早开始的活动时长较长,必然会导致,大部分活动插入不进去。

2.1.4 策略四

还是回到策略一上面来,以时长最短的活动为优先,理论上是没有错的,但是不能简单的仅以时长作为排序依据。

可以以活动结束时间为依据。活动结束的越早,活动时长相对而言是较短的。因同时安排的活动需要有相容性,所以,再选择时,可选择开始时间等于或晚于前一个活动结束时间的活动,这也是与策略一不同之处,并不是简单的选择相容且绝对时长短的活动。

在活动时长较短情况,又能保证教室的使用时间一直连续的,则必然可以安排的活动最多。如下图所示:

  • 选择最早结束的活动。
  • 开始时间与上一次结束时间相容的活动。
  • 如此,反复。且保证活动的相容同时保持了时间上的连续性。

5.png

2.2 编码实现

#include <iostream>
#include <algorithm>
#define MAX  5
using namespace std;
/*
*描述活动的结构体
*/
struct Activity {
	//活动开始时间
	int startTime;
	//活动结束时间
	int endTime;
	//是否选择
	bool isSel=false;
	void desc() {
		cout<<this->startTime<<"-"<<this->endTime<<endl;
	}
};
//所有活动
Activity acts[MAX];
//初始化活动
void initActivities() {
	for(int i=0; i<MAX; i++) {
		cout<<"输入活动的开始时间-结束时间:"<<endl;
		cin>>acts[i].startTime>>acts[i].endTime;
	}
}
/*
*比较函数
*/
bool cmp(const Activity &act0, const Activity &act1) {
	return  act0.endTime<act1.endTime;
}
/*
*活动安排
*/
int getMaxPlan() {
	//计数器
	int count=1;
	//当前选择的活动
	int currentSel=0;
	acts[currentSel].isSel=true;
	for(int i=1; i<MAX; i++) {
		if(acts[i].startTime>=acts[currentSel].endTime ) {
			count++;
			currentSel=i;
			acts[i].isSel=true;
		}
	}
	return count;
}
/*
*输出活动
*/
void showActivities() {
	for(int i=0; i<MAX; i++) {
		if(acts[i].isSel==true)acts[i].desc();
	}
}
/*
*测试
*/
int main() {
	initActivities();
	//排序,使用 STL 算法中的 sort
	sort(acts,acts+MAX,cmp);
	getMaxPlan();
	showActivities();
	return 0;
}

输出结果:

6.png

3. 调度问题

3.1 多机调度

问题描述:

  • n个作业组成的作业集,可由m台相同机器加工处理。
  • 要求使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。
  • 作业不能拆分成更小的子作业。

分析问题:

此问题是可以使用贪心算法的,可以把每一个作业当成一个子问题看待。

  • n<=m时,可满足每一个作业同时有一台机器为之服务。最终时间由最长作业时间决定。
  • n>m时,则作业之间需要轮流使用机器 ,这时有要考虑贪心策略问题。

本问题和上题差异之处:不在意作业什么时候完成,所以作业不存在开始时间和结束时间,每个作业仅有一个作业时长属性。所以,贪心策略也就只有 2 个选择:

  • 按作业时长从小到大轮流使用机器。

如果是求解在规定的时间之内,能完成的作业数量最多,则可以使用此策略。

而本题是求解最终最短时间,此策略就不行。在几个作业同时工作时,**最终完成的的时长是由最长时长的作业决定的。**这是显然易见的道理。一行人行走,总是被行动被慢的人拖累。

如果把最长时长的作业排在最后,则其等待时间加上自己的作业时长,必然会导致最终总时长不是最优解。

  • 按作业时长从大到小轮流使用机器。

先安排时长较长的作业,最理想状态可以保证在它工作时,其它时长较小的作业在它的周期内完成。

7.png

3.2 编码实现

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

//每个作业的工作时长
int* workTimes;
//机器数及状态
int* machines;

/*
*初始化作业
*/
void initWorks(int size) {
	workTimes=new int[size];
	for(int i=0; i<size; i++) {
		cout<<"输入作业的时长"<<endl;
		cin>>workTimes[i];
	}
}
/*
*比较函数
*/
bool cmp(const int &l1, const int &l2) {
	return  l1>l2;
}

/*
*调度
* size: 作业多少
* count:机器数量
*/
int attemper(int size,int count) {
	int totalTime=0;
	int minTime=0;
	int minInit=workTimes[0];
	int k=0;
	vector<int> vec;
	for(int i=0; i<size;) {
		//查找空闲机器
		for(int j=0; j<count; j++) {
			if( workTimes[i]!=0 && machines[j]==-1  ) {
				//安排作业
				machines[j]=i;
				//记录已经安排的作业
				vec.push_back(i);
				i++;
			}
		}
		//找最小时长作业
		minTime=minInit;
		for(int j=0; j<vec.size(); j++) {
			if( workTimes[ vec[j] ] !=0 &&  workTimes[ vec[j] ] < minTime ) {
				minTime= workTimes[ vec[j] ];
				//记下作业编号
				k=vec[j];
			}
		}
		totalTime+=minTime;
		//清除时间
		for(int j=0; j<vec.size(); j++) {
			if(workTimes[vec[j]]>=minTime )
				workTimes[vec[j]]-=minTime;
		}

		for(int j=0; j<count; j++) {
			//机器复位为闲
			if(machines[j]==k)machines[j]=-1;
		}

	}
	//最大值
	int ma=0;
	for(int i=0; i<size; i++) {
		if(workTimes[i]!=0 && workTimes[i]>ma )ma=workTimes[i];
	}
	totalTime+=ma;
	return totalTime;
}
/*
*测试 
*/
int main() {
	cout<<"请输入作业个数:"<<endl;
	int size;
	cin>>size;
	initWorks(size);
	//对作业按时长大到小排序
	sort(workTimes,workTimes+size,cmp);
	cout<<"请输入机器数量"<<endl;
	int count=0;
	cin>>count;
	//初始为空闲状态
	machines=new int[count] ;
	for(int i=0; i<count; i++)machines[i]=-1;
	int res= attemper(size,count);
	cout<<res;
	return 0;
}

4.背包问题

背包问题是类型问题,不是所有的背包问题都能使用贪心算法。

  • 不能分割背包问题也称为0,1背包问题不能使用贪心算法。
  • 可分割的背包问题则可以使用贪心算法。

问题描述:

现有一可容纳重量为 w的背包,且有不同重量的物品,且每一个物品都有自己的价值,请问,怎么选择物品,才能保证背包里的价值最大化。

如果物品不可分,则称为0~1问题,不能使用贪心算法,因为贪心算法无法保证背包装满。一般采用动态规划和递归方案。

如果物品可分,则可以使用贪心算法,本文讨论是可分的情况。

分析问题:

既然可以使用贪心算法,则现在要考虑贪心策略。

4.1 贪心策略

物品的有 2 个属性:

  • 重量。
  • 价值。

直观的策略有 2 个:

  • 贪重量:先放重量最轻的或重量最重的。显然,这个策略是有问题的。以重为先,相当于先放较重的石头,再放较经的金子。以轻为先,相当于先放一堆类似于棉花的物品,最后再放金子。都无法得到背包中的价值最大化。
  • 贪价值:以价值高的优先,理论上可行。但是,如果以纯价值优先,如下图所示,无法让背包价值最大化。因为把物品 3、物品4、物品 5 一起放入背包,总价值可以达到254,且背包还可以容纳其它物品。

8.png

显然,以纯价值优先,是不行。

为什么以纯价值优先不行?

因为没有把价值和重量放在一起考虑。表面上一个物品的价值是较大的,这是宏观上的感觉。但是,这个物品是不是有较高的价值,应该从重量和价值比考虑,也就是价值密度。

Tips: 一块铁重10克,价值为 100。一块金子重 1 克,价值为 90。不能仅凭价值这么一个参数就说明铁比金子值钱。

所以,应该以重量、价值比高的物品作为贪心策略。

4.2 编码实现

#include<iostream>
#include <algorithm>
using namespace std;
/*
*物品类型
*/
struct Goods {
	//重量
	double weight;
	//价值
	double price;
	void desc() {
		cout<<"物品重量:"<<this->weight<<"物品价值:"<<this->price;
	}
};

//所有物品
Goods allGoods[10];
//物品数量
int size;
//背包重量
int bagWeight;
//存储结果,初始值为 0
double result[10]= {0.0};

/*
*初始化物品
*/
void initGoods() {
	for(int i=0; i<size; i++) {
		cout<<"请输入物品的重量、价值"<<endl;
		cin>>allGoods[i].weight>>allGoods[i].price;
	}
}

/*
*用于排序的比较函数
*/
bool cmp( const Goods &g1,const Goods &g2) {
	//按重量价值比
	return g1.price/g1.weight>g2.price/g2.weight;
}
/*
*贪心算法求可分割背包问题
*/
void knapsack() {
	int i=0;
	for(; i<size; i++) {
		if( allGoods[i].weight<=bagWeight ) {
			//如果物品重量小于背包重量,记录下来
			result[i]=1;
			//背包容量减小
			bagWeight-= allGoods[i].weight;
		} else
			//退出循环
			break;
	}
	//如果背包还有空间
	if(bagWeight!=0) {
		result[i]=bagWeight/allGoods[i].weight;
	}
}
/*
*显示背包内的物品
*/
void showBag() {
	double maxValue=0;
	for(int i=0; i<size; i++) {
		if( result[i] ==0)continue;
		allGoods[i ].desc();
		maxValue+=allGoods[i ].price*result[i];
		cout<<"数量"<<result[i]<<endl;
	}
	cout<<"总价值:"<<maxValue<<endl;
}
/*
*测试
*/
int main() {
	cout<<"请输入物品数量"<<endl;
	cin>>size;
	cout<<"请输入背包最大容纳重量"<<endl;
	cin>>bagWeight;
	initGoods();
	//排序
	sort(allGoods,allGoods+size,cmp);
	knapsack();
	showBag();
}

输出结果:
9.png

5. 过河

问题描述:

有一艘小船,每次只能载两个人过河。其中只能一人划船,渡河的时间等于其中划船慢的那个人的时间。现给定一群人和他们的划船时间,请求解最小全部渡河的时间。

分析问题:

可根据人数分情况讨论。如下n表示人数:

当总人数n<=3时:

  • n=1, t=t[1]。只有一个人时,过河时间为此人划船时间。
  • n=2 t=max(t[1],t[2])。当只有二个人时, 12中耗时长的那个。
  • n=3 t=t[1]+t[2]+t[3]13先去,1回来,再和2去。

否则,按划船的时间由快到慢排序。
10.png

基本原则,回来时,选择最快的。

  • 1(最快)n(最慢)先去,1(最快)回来,1n-1(次慢)去,1(最快)的回来 , n-=2, 用时2*t[1]+t[n]+t[n-1]

11.png

12.png

13.png

14.png

  • 1(最快)2(次快)去,1(最快)回来,nn-1去,2回来, n-=2 用时t[1]+2*t[2]+t[n]

15.png

16.png

17.png

18.png

  • 每次取两种方法的最小耗时,直到n<=3

编码实现:

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

//每个人的过河时间
int times[100];

/*
*初始化过河时间
*/
void initTimes(int size) {
	for(int i=0; i<size; i++) {
		cout<<"输入时间"<<endl;
		cin>>times[i];
	}
}

/*
*比较函数
*/
bool cmp(const int &i, const int &j) {
	return  i<j;
}

/*
*贪心算法计算过河最短时间
*/
int river(int size) {
	int totalTime=0;
	int i=0;
	int j=size-1;

	while(1) {
		if(j-i<=2)break;
		int t1=2*times[0]+times[j]+times[j-1];
		int t2=times[0]+2*times[1]+times[j];
		totalTime+=min(t1,t2);
		j-=2;
	}
	if(j==0)totalTime+= times[0];
	if(j==1)totalTime+= max(times[0],times[1] );
	if(j==2)totalTime+=times[0]+times[1]+times[2];

	return totalTime;
}

int main() {
	cout<<"过河人数:"<<endl;
	int size=0;
	cin>>size;
	initTimes(size);
	//排序
	sort(times,times+size,cmp);
	int res= river(size);
	cout<<"最短时间:"<<res;
	return 0;
}

输出结果:

19.png

6. 总结

贪心算法在很多地方就可看其身影。如最短路径查找、最小生成树算法里都用到贪心算法。

贪心算法的关键在于以什么属性优先(贪心策略),这个选对了,后续实现都很简单。

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

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

相关文章

Seata-Server分布式事务原理加源码 (五) - Seata配置Nacos注册中心和配置中心

Seata配置Nacos注册中心和配置中心 Seata支持注册服务到Nacos&#xff0c;以及支持Seata所有配置放到Nacos配置中心&#xff0c;在Nacos中统一维护&#xff1b; 高可用模式下就需要配合Nacos来完成 具体配置如下 注册中心 Seata-server端配置注册中心&#xff0c;在registr…

【Android学习】下载jar慢和gradle慢的情况

目录 问题出现的原因 解决方法 解决Gradle下载问题&#xff1a;手动安装 解决jar包下载慢问题&#xff1a;更改下载源 问题出现的原因 国内访问谷歌被墙导致访问速度慢或者干脆无法下载 解决方法 解决Gradle下载问题&#xff1a;手动安装 访问官网Gradle | Release Candi…

配置可视化-基于form-render的无代码配置服务(一)

背景 有些业务场景需要产品或运营去配置JSON数据提供给开发去使用&#xff08;后面有实际业务场景的说明&#xff09;&#xff0c;原有的业务流程&#xff0c;非开发人员&#xff08;后面直接以产品指代&#xff09;把数据交给开发&#xff0c;再由开发去更新JSON数据。对于产…

【LeetCode】打家劫舍 III [M](递归)

337. 打家劫舍 III - 力扣&#xff08;LeetCode&#xff09; 一、题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识…

山东大学2022操作系统期末

接力&#xff1a;山东大学2021操作系统期末 2022—2023山东大学计算机操作系统期末考试回忆版 简答题(4 10 points) &#xff08;1&#xff09;用户态&#xff0c;核心态是什么 &#xff08;2&#xff09;这种区分对现代操作系统的意义 &#xff08;3&#xff09;printf(“…

基于RK3399+STM32+PID的四轴飞行器跟踪与控制系统设计

系统硬件的总体方案设计 要设计一款具有跟踪功能且飞行稳定的四轴飞行器跟踪系统&#xff0c;首先要保证系 统硬件平台的功能稳定。系统各模块具有不同功能&#xff0c;所以需要根据各模块功能与 性能&#xff0c;进行芯片的选取与硬件电路设计&#xff0c;使系统在经济性、生产…

优维低代码:Legacy Templates 构件模板

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 连载…

镜像恒流源电路分析

在改进型差动放大器中&#xff0c;用恒流源取代射极电阻RE&#xff0c;既为差动放大电路设置了合适的静态工作电流&#xff0c;又大大增强了共模负反馈作用&#xff0c;使电路具有了更强的抑制共模信号的能力&#xff0c;且不需要很高的电源电压&#xff0c;所以&#xff0c;恒…

Chatgpt取代客服?取代客服的其实另有其人

近来&#xff0c;一款聊天机器人ChatGPT刷爆全网&#xff0c;这也让不少人发出疑问&#xff1a;人工智能真的能大规模取代人类吗&#xff1f;其实&#xff0c;人工智能在很多行业比如电商、金融、医疗教育和制造业等领域已经有许多尝试和应用&#xff0c;最常见的就是大家在生活…

研报精选230214

目录 【行业230214艾瑞股份】中国增强现实&#xff08;AR&#xff09;行业研究报告【行业230214国信证券】信息安全深度剖析5&#xff1a;密评和信创双催化&#xff0c;密码产业开启从1到N【行业230214民生证券】磁性元器件深度报告&#xff1a;乘新能源之风&#xff0c;磁性元…

【数据结构】基础:图的遍历实现(附C++源代码)

【数据结构】基础&#xff1a;图的遍历实现&#xff08;附C源代码&#xff09; 摘要&#xff1a;将会在数据结构专题中开展关于图论的内容介绍&#xff0c;其中包括四部分&#xff0c;分别为图的概念与实现、图的遍历、图的最小生成树以及图的最短路径问题。本文将介绍图的遍历…

Python实现视频自动打码功能,避免看到羞羞的画面

前言 嗨呀嗨呀&#xff0c;最近重温了一档综艺节目 至于叫什么 这里就不细说了 老是看着看着就会看到一堆马赛克&#xff0c;由于太好奇了就找了一下原因&#xff0c;结果是因为某艺人塌房了…虽然但是 看综艺的时候满影响美观的 咳咳&#xff0c;这里我可不是来教你们如何解…

卡诺图化简

1.相关概念 最小项&#xff1a;函数的某个乘积项包含了函数的全部变量&#xff08;原变量或反变量的形式&#xff09;&#xff0c;且每个变量仅出现一次&#xff0c;则这个乘积项为该函数的一个标准积项。 最小项中的原变量记为1&#xff0c;反变量记为0&#xff0c;当变量顺序…

C++STL剖析(九)—— unordered_map和unordered_multimap的概念和使用

文章目录1. unordered_map的介绍和使用&#x1f351; unordered_map的构造&#x1f351; unordered_map的使用&#x1f345; insert&#x1f345; operator[ ]&#x1f345; find&#x1f345; erase&#x1f345; size&#x1f345; empty&#x1f345; clear&#x1f345; sw…

程序环境和预处理详解

文章目录一、程序环境1.1 - 翻译环境1.1.1 - 编译1.1.1.1 - 预编译&#xff08;预处理&#xff09;1.1.1.2 - 编译1.1.1.3 - 汇编1.1.2 - 链接1.2 - 执行环境二、预处理详解2.1 - 预定义符号2.2 - #define2.2.1 - #define 定义标识符2.2.1.1 - 语法2.2.1.2 - 建议2.2.2 - #defi…

AI极大地改变了知识创造与分发的逻辑

AI改变了&#xff5e;知识创造、分发的逻辑 细想一下这是恐怖的 已经传导出给教育的压力 趣讲大白话&#xff1a;AI机器人成了随身专家 *********** 1.以前靠秀才创造、分发知识 2.后来是教育体系为主 3.再后&#xff0c;互联网平台聚合和分发 4.将来可能大部分是机器人创造、分…

Xshell和Xftp的下载和在linux虚拟机中的使用

Xshell和Xftp的下载和在linux虚拟机中的使用一、Xshell和Xftp简介XshellXftp二、 Xshell和Xftp下载三、Xshell和Xftp安装Xshell安装Xftp安装四、 Xshell和Xftp使用找到linux虚拟机的ip地址Xshell的使用Xftp的使用一、Xshell和Xftp简介 Xshell Xshell 是一个强大的安全终端模拟…

对灵敏度分析技术进行建模(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

9、MyBatis框架——使用注解开发实现数据库增删改查操作、一级缓存、二级缓存、MyBatis实现分页

目录 一、使用注解开发实现数据库增删改查操作 1、搭建项目 2、使用注解开发操作数据库 二、一级缓存 1、一级缓存失效的情况 三、二级缓存 1、手动开启二级缓存cacheEnabled 2、二级缓存机制 四、MyBatis实现分页 1、配置环境 2、startPage()开启分页 3、PageInfo…

charles+夜神模拟器抓包

1.资料地址: 链接&#xff1a;https://pan.baidu.com/s/1w9qYfFPJcduN4If50ICccw 提取码&#xff1a;a7xa2.安装charles 和夜神模拟器并配置参考地址: https://www.beierblog.com/archives/%E4%BA%B2%E6%B5%8B%E5%AE%8C%E5%85%A8%E5%8F%AF%E8%A1%8Ccharles%E6%8A%93%E5%8C%85%E…