算法——贪心法
贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
对贪心法的深入理解
(1)原理:一种启发式策略,在每个决策点作出在当时看来最佳的选择
(2)求解最优化问题的两个关键要素:贪心选择性质+最优子结构
①贪心选择性质:进行选择时,直接做出在当前问题中看来最优的选择,而不必考虑子问题的解;
②最优子结构:如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质
(3)解题关键:贪心策略的选择
贪心算法不是对所有问题都能得到整体最优解的,因此选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
(4)一般步骤:
①建立数学模型来描述最优化问题;
②把求解的最优化问题转化为这样的形式:对其做出一次选择后,只剩下一个子问题需要求解;
③证明做出贪心选择后:
a.原问题总是存在全局最优解,即贪心选择始终安全;
b.剩余子问题的局部最优解与贪心选择组合,即可得到原问题的全局最优解。
与动态规划的联系
如果大家比较了解动态规划,就会发现它们之间的相似之处。最优解问题大部分都可以拆分成一个个的子问题,把解空间的遍历视作对子问题树的遍历,则以某种形式对树整个的遍历一遍就可以求出最优解,大部分情况下这是不可行的。贪心算法和动态规划本质上是对子问题树的一种修剪,两种算法要求问题都具有的一个性质就是子问题最优性(组成最优解的每一个子问题的解,对于这个子问题本身肯定也是最优的)。动态规划方法代表了这一类问题的一般解法,我们自底向上构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。而贪心算法是动态规划方法的一个特例,可以证明每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。
钱币找零问题
这个问题在我们的日常生活中就更加普遍了。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。在程序中已经事先将Value按照从小到大的顺序排好。
#include#includeusing namespace std;const int N=7; int Count[N]={3,0,2,1,0,3,5};int Value[N]={1,2,5,10,20,50,100}; int solve(int money) { int num=0; for(int i=N-1;i>=0;i--) { int c=min(money/Value[i],Count[i]); money=money-c*Value[i]; num+=c; } if(money>0) num=-1; return num;} int main() { int money; cin>>money; int res=solve(money); if(res!=-1) cout<endl; else cout<<"NO"<<endl;}
最最最经典的背包问题
一共有三个背包问题
零一背包问题:有n种重量和价值分别为Wi和Vi的物品。从这些物品中挑选出总重量不超过w的物品,每种物品都只能挑选一件,求所有挑选方案中价值总和的最大值。
完全背包问题:有n种重量和价值分别为Wi和Vi的物品。从这些物品中挑选出总重量不超过w的物品,每种物品都可以挑选多件,求所有挑选方案中价值总和的最大值。
部分背包问题:有n种重量和价值分别为Wi和Vi的物品。从这些物品中挑选出总重量不超过w的物品,第i种物品最多选mi个,求所有挑选方案中价值总和的最大值。
然而我们考虑这样一种背包问题:在选择物品i装入背包时,可以选择物品的一部分,而不一定要全部装入背包。这时便可以使用贪心算法求解了。计算每种物品的单位重量价值作为贪心选择的依据指标,选择单位重量价值最高的物品,将尽可能多的该物品装入背包,依此策略一直地进行下去,直到背包装满为止。在零一背包问题中贪心选择之所以不能得到最优解原因是贪心选择无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。
代码附上:
#includeusing namespace std; const int N=4; void knapsack(float M,float v[],float w[],float x[]); int main() { float M=50; //背包所能容纳的重量 float w[]={0,10,30,20,5}; //每种物品的重量 float v[]={0,200,400,100,10}; //每种物品的价值 float x[N+1]={0}; //记录结果的数组 knapsack(M,v,w,x); cout<<"选择装下的物品比例:"<<endl; for(int i=1;i<=N;i++) cout<<"["<"]:"<} void knapsack(float M,float v[],float w[],float x[]) { int i; //物品整件被装下 for(i=1;i<=N;i++) { if(w[i]>M) break; x[i]=1; M-=w[i]; } //物品部分被装下 if(i<=N) x[i]=M/w[i]; }
注:简单来讲,贪心法就是重点突出在贪心二字,解决问题时先拿价值最高或者重量最大的,然后依次考虑价值或者重量次要的,直到满足要求。但是贪心算法也有一个问题需要注意,有些问题时不适合用贪心算法的。
例如有价值6元、5元、3元的硬币,如何用最少的硬币凑够10元,假如我们用贪心算法的话得到的是一枚6元的硬币和四枚1元硬币,但是正确答案是两枚5元硬币,在此我们需注意这种情况!!!!