贪心算法

本文介绍了贪心算法的基础概念,包括贪心选择和最优子结构,并通过硬币问题和区间问题的例题讲解,展示了贪心算法在解决实际问题中的应用。贪心算法在每次选择时追求局部最优,但并不保证全局最优。最后列举了几个经典的贪心算法问题实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

基础概念

基本要素

基本思路

例题讲解

经典问题


基础概念

在对问题求解时,总是做出在当前看来是最好的选择不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择

必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性

BFS、DFS、动态规划算法是在多种策略中选取最优解

贪心算法遵循某种规则,不断地选取当前最优策略

 

基本要素

1、贪心选择

指所求问题的整体最优解可以通过一系列局部最优的选择达到。是贪心算法可行的第一个基本要素,也是与动态规划算法的主要区别

采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题

2、最优子结构

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质

运用贪心策略在每一次转化时都取得了最优解

问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征

贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是

贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能

动态规划主要运用于二维或三维问题,而贪心一般是一维问题

 

基本思路

1、思想

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

2、过程

  1. 建立数学模型来描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每一子问题求解,得到子问题的局部最优解
  4. 把子问题的解局部最优解合成原来解问题的一个解

 

例题讲解

1、硬币问题

Description

有1元、5元、10元、50元、100元、500元的硬币各 C1、C5、C10、C50、C100、C500 枚。现在要用这些硬币来支付 A元,最少需要多少硬币?假定本题至少存在一种支付方案

限制条件:

1<= C1、C5、C10、C50、C100、C500 <=1e9

0<= A <=1e9

Input

依次输入每种硬币的现有数量,最后输入支付金额

Output

总共需要的硬币数

Sample Input

3 2 1 3 0 2 620

Sample Output

6

题解

#include<bits/stdc++.h>
using namespace std;

const int v[6]={1,5,10,50,100,500}; //硬币面值
//输入
int c[6]; //c[0]=c_1,c[1]=c_5,...
int A; //支付金额
void solve(){
	int ans=0;
	
	for(int i=5;i>=0;--i){
		int t=min(A/v[i],c[i]); //使用的硬币数,注意硬币数量有限 c[i] 
		A-= t*v[i];
//		//输出支付硬币的面值和数目 
//		if(0!=t){
//			printf("面值为%d的硬币%d枚\n",v[i],t);
//		}
		ans+=t;
	}
	
	printf("%d\n",ans);
}

int main(){
	for(int i=0;i<6;++i){
		scanf("%d",&c[i]);
	}
	scanf("%d",&A);
	solve();
	return 0;
}

按照直觉,大面值硬币用得越多,使用的硬币数越少,因此尽量按照面值由大到小的顺序使用硬币,可尽可能的满足题设


2、区间问题

Description

有 N 项工作,每项工作分别在 Si 时间开始,在 Ti 时间结束。对于每项工作,你都可以选择参与与否。如果选择了参与,那么自始至终都必须全程参与。此外参与工作的时间段不能重叠(开始的瞬间和结束的瞬间重叠也不允许)

你的目标是参与尽可能多的工作,那么最多能参与多少项工作?

限制条件:

1<= Si <= Ti <=1e9

0<= N <=1e5

Input

第一行输入工作项数

第二行输入每项工作的开始时间

第三行输入每项工作的结束时间

Output

最多能参与的工作项数

Sample Input

5

1 2 4 6 8

3 5 7 9 10

Sample Output

3

题解

#include<bits/stdc++.h>
using namespace std;

const int MAX_N=1e5+50;
int n,S[MAX_N],T[MAX_N]; // n为工作项数,S[i]为开始时间,T[i]为结束时间 
pair<int,int> itv[MAX_N]; //用于对工作排序的 pair数组 
void solve(){
	//对 pair数组进行字典序比较
	//为了让结束时间早的工作排在前面,结束时间存入 first,开始时间存入 second 
	for(int i=0;i<n;++i){
		itv[i].first=T[i];
		itv[i].second=S[i];
	}
	sort(itv,itv+n);
	
	// t是最后所选工作的结束时间
	int ans=0,t=0;
	for(int i=0;i<n;++i){
		if(t<itv[i].second){ //下一工作的开始时间晚于上一工作的结束时间 
			++ans;
			t=itv[i].first;
			//输出符合的工作号
			printf("%d\n",i+1); 
		}
	}
	
	printf("%d\n",ans);
} 

int main(){
	scanf("%d",&n);
	for(int i=0;i<n;++i){
		scanf("%d",&S[i]);
	}
	for(int i=0;i<n;++i){
		scanf("%d",&T[i]);
	}
	solve(); 
	return 0;
}

看到这个问题,相信大家能一下想到很多种解法

  1. 在可选的工作中,每次都选择开始时间最早的工作
  2. 在可选的工作中,每次都选择用时最短的工作
  3. 在可选的工作中,每次都选择与最少可选工作有重叠的工作
  4. 在可选的工作中,每次都选择结束时间最早的工作

前三种分别能举出反例

算法一反例

算法二反例

算法三反例

所以算法二正确。根据惯性思维也可以想到,越早结束,越能尽快选择其他工作,参加的工作数就越多

该题有很多类似的问题,参加活动、班级开会等,都是相同的思想

 

经典问题

POJ 1328——Radar Installation(贪心)

POJ 2431——Expedition(优先队列,贪心)

POJ 3069——Saruman's Army(贪心)

POJ 3253——Fence Repair(优先队列,贪心)

POJ 3617——Best Cow Line(字典序比较,贪心)

 

与诸君共勉 ^_^

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值