基础概念
在对问题求解时,总是做出在当前看来是最好的选择。不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择
必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性
BFS、DFS、动态规划算法是在多种策略中选取最优解
贪心算法遵循某种规则,不断地选取当前最优策略
基本要素
1、贪心选择
指所求问题的整体最优解可以通过一系列局部最优的选择达到。是贪心算法可行的第一个基本要素,也是与动态规划算法的主要区别
采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题
2、最优子结构
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质
运用贪心策略在每一次转化时都取得了最优解
问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征
贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是
贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能
动态规划主要运用于二维或三维问题,而贪心一般是一维问题
基本思路
1、思想
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加时算法停止
2、过程
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每一子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来解问题的一个解
例题讲解
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; }
看到这个问题,相信大家能一下想到很多种解法
- 在可选的工作中,每次都选择开始时间最早的工作
- 在可选的工作中,每次都选择用时最短的工作
- 在可选的工作中,每次都选择与最少可选工作有重叠的工作
- 在可选的工作中,每次都选择结束时间最早的工作
前三种分别能举出反例
算法一反例
算法二反例
算法三反例
所以算法二正确。根据惯性思维也可以想到,越早结束,越能尽快选择其他工作,参加的工作数就越多
该题有很多类似的问题,参加活动、班级开会等,都是相同的思想