【算法浅析】贪心算法学习笔记

(一)贪心算法基础
1、贪心算法是什么

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,所得的是某种意义上的局部最优解
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态。一般来说,如果在想到某个似乎可行的策略,并且自己无法举出反例,那么就勇敢地实现它。

2、基本要素
  • 贪心选择
    贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
    它是与动态规划算法的主要区别。贪心选择采用自顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的问题。
  • 最优子结构
    当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
3、算法思想

  贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,它的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止

4、贪心算法过程
  • 建立数学模型来描述问题
  • 把求解的问题分成若干个子问题
  • 对每一子问题求解,得到子问题的局部最优解
  • 把子问题的解局部最优解合成原来解问题的一个解。
5、贪心算法适用的问题一般具有的特性
  • 随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。
  • 有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。
  • 还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。
  • 选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。
  • 最后,目标函数给出解的值。
  • 为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。
4、算法存在的问题
  • 不能保证求得的最后解是最佳的
  • 不能用来求最大值或最小值的问题
  • 只能求满足某些约束条件的可行解的范围
6、算法实现
  • 从问题的某个初始解出发
  • 采用循环语句,当可以向求解目标前进一步时,就根绝局部最优策略,得到一个部分解,缩小问题的范围或规模。
  • 将所有部分解综合起来,得到问题的最终解。
(二)简单贪心实例
1、【PAT B1020】月饼
  • 题目描述

  月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价以及市场的最大需求量,试计算可以获得的最大收益是多少。
  注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如有三种月饼,存量分别为18/15/10万吨,总售价分别为75、72、45亿元。如果市场的最大需求量只有20万吨,那么最大收益策略应该是卖出全部15万吨第二种月饼以及5万吨第三种月饼,得72+45/2=94.5(亿元)。

  • 输入格式

 每个输入包含1个测试用例。每个测试用例先给出一个不超过1000的正整数N表示月饼的种类数以及不超过500(以万吨为单位)。数字间以空格分隔。

  • 输出格式

对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后两位。

  • 输入样例

3 20
18 15 20
45 72 45

  • 输出样例

94.5

  • 题意

 现有月饼需求量为D,已知n种月饼各自的库存量和总售价,问如何销售这些月饼,使得可以收获的利益最大。求最大收益。

  • 思路
    步骤一:采用“总是选择单价最高的月饼出售,可以获得最大的利润”的策略。因此,对每种月饼,都根据其库存量和总售价来计算出这种月饼的单价。之后,将所有月饼按单价从高到低排序。
    步骤二:从单价高的月饼开始枚举。
    ①如果该种月饼的库存量不足以填补所有需求量,则将该种月饼全部卖出,此时需求量减少该种月饼的库存量大小,收益值增加该种月饼的总售价大小。
    ②如果该种月饼的库存量足够供应需求量,则只提供需求量大小的月饼,此时收益值增加当前需求量乘以该种月饼的单价,而需求量减为0.
    这样,最后得到的收益值即为所求的最大收益值。
  • 策略正确性的证明

 假设有两种单价不同的月饼,其单价分别为a和b(a<b)。如果当前需求量为K,那么两种月饼的总收入分别为aK与bK,而aK<bK显然成立,因此需要出售单价更高的月饼。

  • 注意事项
    ①月饼库存量和总售价可以是浮点数(题目中只说是正数,没说是正整数),需要用double型存储。对于,总需求量D虽然题目说是正整数,但是为了后面计算方便,也需要定义为浮点型。
    ②当月饼库存量高于需求量时,不能先令需求量为0,然后再计算收益,这会导致收益为0.
    ③当月饼库存量高于需求量时,要记得将循环中断,否则会出错。

  • 代码

#include <iostream>
#include<algorithm>
using namespace std;
struct mooncake{
	double store;//库存量
	double sell;//总售价
	double price;//单价 
}cake[1010];
//按单价从高到低排序
bool cmp(mooncake a,mooncake b){
	return a.price>b.price;
} 
int main(int argc, char** argv) {
	int n;
	double D;//总需求量 
	scanf("%d%lf",&n,&D);
	for(int i=0;i<n;i++){
		scanf("%lf",&cake[i].store);
	}
	for(int i=0;i<n;i++){
		scanf("%lf",&cake[i].sell);
		//计算 单价 
		cake[i].price=cake[i].sell/cake[i].store;
	}
	sort(cake,cake+n,cmp);
	double ans=0;//收益
	for(int i=0;i<n;i++){
		//如果需求量高于月饼库存量 
		if(cake[i].store<=D){
			//第i种月饼全部卖出
			D-=cake[i].store;
			ans+=cake[i].sell; 
		}else{
			//如果月饼库存量高于需求量
			ans+=cake[i].price*D;//只卖出剩余需求量的月饼
			break; 
		}
	}
	printf("%.2f\n",ans);
		
	return 0;
}
2、【PAT B1023】组个最小数
  • 题目描述

 给定数字0~9各若干个。可以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(注意:0不能做首位)。例如,给定两个0、两个1、三个5和一个8,得到的最小的数就是10015558.
 现给定数字,请编写程序输出能够组成的最小的数。

  • 输入格式

 每个输入包含1个测试用例。每个测试用例在一行中给出十个非负整数,顺序表示所有数字0、数字1…数字9的个数。整数间加一个空格分隔。十个数字的总个数不超过50,且至少拥有一个非0的数字。

  • 输出格式

在一行中输出能够组成的最小的数。

  • 输入样例

2 2 0 0 0 3 0 0 1 0

  • 输出样例

10015558

  • 思路
     策略是:先从1 ~ 9中选择个数不为0的最小的数输出,然后从0~9输出数字,每个字输出次数为其剩余个数。
     以样例为例,最高位为个数不为0的最小的数1,此后1的剩余个数减1(由2变为1)。接着按剩余次数(0剩余两个,1剩余一个,5出现三个,8出现一个)依次输出所有数。
  • 策略正确性的证明

 首先,由于所有数字都必须参与组合,因此最后结果的位数是确定的。然后,由于最高位不能为0,因此需要从[1,9]中选择最小的数输出(如果存在两个长度相同的数的最高位不同,那么一定是最高位小的数更小)。最后,针对除最高位外的所有位,也是从高位到低位优先选择[0,9]中还存在的最小的数输出。

  • 注意点

 由于第一位不能是0,因此第一个数字必须从1~ 9中选择最小的存在的数字,且找到这样的数字之后要及时中断循环。

  • 参考代码
#include <iostream>
#include<algorithm>
using namespace std;
int main(){
	int count[10];//记录数字0~9的个数
	for(int i=0;i<10;i++){
		scanf("%d",&count[i]);
	}
	for(int i=1;i<10;i++){
		//从1~9中选择count不为0的最小的数字 
		if(count[i]>0){
			printf("%d", i);
			count[i]--;
			break;//找到一个之后就中断 
		} 
	}
	for(int i=0;i<10;i++){
		//从0~9中输出对应个数的数字
		for(int j=0;j<count[i];j++){
			printf("%d",i);
		} 
	} 
	
	return 0;
}
3、背包问题
  • 问题描述

有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

物品重量价值
A3510
B3040
C6030
D5050
E4035
F1040
G2530

  • 问题分析
    ①目标函数: ∑pi最大,使得装入背包中的所有物品pi的价值加起来最大。
    ②约束条件:装入的物品总重量不超过背包容量:∑wi<=M( M=150)
    ③贪心策略:
    (1)选择价值最大的物品
    (2)选择重量最小的物品
    (3)选择单位重量价值最大的物品
    有三个物品A,B,C,其重量分别为{30,10,20},价值分别为{60,30,80},背包的容量为50,分别应用三种贪心策略装入背包的物品和获得的价值如下所示:
  • 贪心策略1(按价值最大)
物品价值重量
B12030
A6020
  • 贪心策略2(按重量最小)
物品价值重量
C5010
A6020
  • 贪心策略3(按单位重量价值最大)
物品单位价值重量
C50/10=510
B12./30=430
  • 算法设计
    ①计算出每个物品单位重量的价值
    ②按单位价值从大到小将物品排序
    ③根据背包当前所剩容量选择物品
    ④如果背包的容量大于当前物品的重量,那么就将当前物品装进去。否则,就将当前物品舍去,然后跳出循环结束。

  • 参考代码

#include<iostream>
#include<algorithm>
using namespace std;
typedef struct{
    int w;
    int v;
    double avg;
}P;
bool cmp(P a,P b){
    return a.avg>b.avg;
}
int main(){
    P *p;
    int n,i,m;//n 物品个数 m背包容量
    while(cin>>n>>m){
        p=new P[n];
        for(i=0;i<n;i++){
            cin>>p[i].w>>p[i].v;
            p[i].avg=p[i].v/p[i].w*1.0;
        }
        sort(p,p+n,cmp);
        int maxvalue=0;
        for(i=0;i<n;i++){
            if(p[i].w<=m){
                m-=p[i].w;
                maxvalue+=p[i].v;
            }else{
                break;
            }
        }
        cout<<maxvalue<<endl;
    }
    return 0;
}
(三)区间贪心实例
1、区间不相交问题
  • 问题描述

 给出N个开区间(x,y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。例如对开区间(1,3)、(2,4)、(3,5)、(6,7)来说,可以选出最多三个区间(1,3)、(3,5)、(6,7),它们互相没有交集。

  • 问题分析
      首先考虑最简单的情况,如果开区间 I1被开区间 I2包含,如下图a)所示,那么显然选择I1是最好的选择,因为如果选择I1,那么就有更大的空间去容纳其他开区间。
    接下来把所有开区间按左端点x从小到大排序,如果去除掉区间包含的情况,那么一定有y1>y2>…>yn成立,如图b)所示。现在考虑应当如何选取区间。通过观察会发现,I1的右边有一段是一定不会和其他区间重叠的,如果把它去掉,那么I1的左边剩余部分就会被I2包含,由图a)可知,应当选择I1.因此对这种情况,总是先选择左端点最大的区间。
    在这里插入图片描述
  • 参考代码
#include <iostream>
#include<algorithm>
using namespace std;
const int maxn=110;
struct Inteval{
	int x,y;//开区间左右端点 
}I[maxn];

bool cmp(Inteval a,Inteval b){
	if(a.x!=b.x){
		//先按左端点从大到小排序 
		return a.x>b.x;
	}else{
		//左端点相同的按右端点从小到大排序
		return a.y<b.y; 
	} 
}
int main(){
	int n;
	while(scanf("%d",&n),n!=0){
		for(int i=0;i<n;i++){
			scanf("%d%d",&I[i].x,&I[i].y);
		}
		sort(I,I+n,cmp);//把区间排序
		//ans记录不相交区间个数,lastX记录上一个被选中区间的左端点
		int ans=1,lastX=I[0].x;
		for(int i=1;i<n;i++){
			//如果该区间右端点在lastX左边 
			if(I[i].y<=lastX){
				lastX=I[i].x;//以I[i]作为新选中的区间
				ans++;//不相交区间个数加1 
			}
		}
		printf("%d\n",ans); 
	} 
} 
(四)参考文献

【1】五大常用算法之一:贪心算法
【2】五大基本算法之贪心算法 Greedy
【3】贪心算法—机器之心
【4】贪心算法总结
【5】算法笔记

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 算法个人学习笔记pdf是一种以电子文档形式呈现的算法学习笔记资源。在这个pdf中,个人学习者可以记录和整理自己在学习算法过程中的思考、心得和解题方法。在这个学习笔记pdf中,个人学习者可以自由地添加和编辑自己的学习内容,包括算法的原理、算法实现的代码以及相应的思路和分析。通过这种方式,个人学习者可以更系统地学习和理解算法,并且能够随时查看自己的学习进展和学习成果。 通过编写和整理算法个人学习笔记pdf,个人学习者可以更好地理解和应用学习到的算法知识。这种记录和整理的过程可以帮助个人学习者更深入地思考问题和解决问题的方法,同时也可以帮助个人学习者更好地复习和回顾已学习算法知识。 对于其他学习者来说,算法个人学习笔记pdf也是一个宝贵的学习资源。其他学习者可以通过查阅个人学习者的学习笔记pdf,借鉴和学习其中的思路和方法。这样可以帮助其他学习者更好地理解和应用算法知识,同时也可以促进知识的分享和交流。 总的来说,算法个人学习笔记pdf是一个为个人学习者提供记录和整理学习过程的工具,同时也是一个为其他学习者提供学习资源和参考的媒介。通过编写和整理算法个人学习笔记pdf,个人学习者可以更好地学习和理解算法知识,同时也可以促进算法知识的分享和交流。 ### 回答2: 算法个人学习笔记pdf是一份记录个人学习算法的文档,具有以下特点和优势。 首先,这份学习笔记是以PDF格式保存的,这意味着可以在任何设备上方便地查看和阅读,无需依赖特定的平台或软件。无论是在电脑、平板还是手机上,都可以轻松地浏览和学习。 其次,这份学习笔记是个人整理的,因此具有个性化的特点。不同的人在学习算法时可能会关注和理解的重点有所不同,通过个人学习笔记,可以反映出个人对算法知识的理解和思考。这样的学习笔记对于个人的学习和复习过程非常有帮助。 此外,这份学习笔记应当具有清晰的结构和逻辑。算法知识通常是有层次结构的,基本的知识点和概念通常是必须掌握的基础,而进阶的知识则需要在掌握基础知识的基础上构建。学习笔记应当按照这个结构和逻辑进行组织,便于学习者理解和掌握。 最后,这份学习笔记应当具有实例和练习题。算法知识的学习不能仅仅停留在理论层面,还需要通过实际的例子和练习题进行实践和巩固。学习笔记应当包含这些实例和练习题,并给出相应的解析和答案,方便学习者进行练习和巩固。 总而言之,算法个人学习笔记pdf是一份方便、个性化、结构清晰、包含实例和练习题的文档,对于学习者来说非常有价值。 ### 回答3: 算法学习笔记PDF是一份用于记录个人学习算法的文档。通过编写学习笔记,我可以对算法的理论和实践有更深入的理解和掌握。 首先,在学习算法的过程中,理论与实践结合是非常重要的。在学习笔记中,我可以记录下算法的原理和相关的数学推导,以及对应的代码实现和应用场景。通过这样的记录方式,我可以更好地理解算法的本质和使用方式。 其次,学习笔记可以帮助我回顾和巩固所学的知识。通过整理和总结学习笔记,我可以梳理出算法的基础知识和重要思想,并将其记忆固定下来。同时,学习笔记也可以作为复习的资料,提供方便快捷的回顾方式。 此外,学习笔记还可以促进自我思考和学习方法的改进。在编写笔记的过程中,我可以思考和提出自己的问题,并通过查阅相关资料和与他人讨论,来找到问题的答案和解决方案。这样的思考过程可以帮助我提高问题解决的能力和学习效果。 最后,学习笔记可以与他人分享和交流。通过分享学习笔记,我可以与其他学习者进行交流和讨论,互相学习和提高。同时,学习笔记也可以作为自己学习和成长的见证,激励自己坚持学习和进步。 总之,算法个人学习笔记PDF是一份记录、回顾、思考和分享的文档,对于个人的算法学习具有重要的意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值