1.贪心算法简介
1.1 基本定义
在贪婪算法(greedy method) 中,我们要逐步构造
一个最优解。每一步,我们都在一定的标准下,做出一个最优决策。做出决策所依据的标准称为贪心准则(greedy criterion)。
贪心算法是指,在对问题求解时,总是做出在
当前看来是最好的选择
。
也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解
。
贪心算法每一步必须满足以下条件:
1、可行的:即它必须满足问题的约束。
2、局部最优:他是当前步骤中所有可行选择中最佳的局部选择。
3、不可更改:即选择一旦做出,在算法的后面步骤就不可改变了。
注意:!!!
贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
1.2 贪心算法案例
钱币找零问题
假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来找给顾客K元,怎么用数目最少的钱来找零?
贪心准则:在不超过要找的零钱总数的条件下,每一次都选择面值尽可能大的纸币,直到凑成的零钱总数等于要找的零钱总数。
#include<iostream>
using namespace std;
int min(int a, int b) {
return a < b ? a : b;
}
int main()
{
//人民币面值集合
int values[] = { 1, 2, 5, 10, 20, 50, 100 };
//各种面值对应数量集合
int counts[] = { 3, 1, 2, 1, 1, 3, 5 };
//求442元人民币需各种面值多少张
//用来记录需要的各种面值张数
int money = 442;
int len = sizeof(values) / sizeof(values[0]);
int* result = new int[len];
for (int i = len - 1; i >= 0; i--) {
int num = 0; //当前面值纸币的数量
num = min(money / values[i], counts[i]); //当前纸币可以找的最大数量
money = money - num*values[i];
result[i] = num;
}
//输出最后结果
for (int i = 0; i < len; i++) {
if(result[i])
cout << "需要面额为" << values[i] << "的人名币" << result[i] << "张\n";
}
cout << endl;
system("pause");
return 0;
}
程序结果:
需要面额为2的人名币1张
需要面额为5的人名币2张
需要面额为10的人名币1张
需要面额为20的人名币1张
需要面额为100的人名币4张
可以得出,求出的结果为最优解,但是,当纸币面值和数量为某些特殊情况下,贪心算法就无法给出最优解。但是,贪心算法往往能给出近似解,对于我们来说也是有价值的。
比如对于纸币有1、5、7面值的若干,要凑出10元
贪心解[3,0,1]
最优解[0,2,0]
1.3.贪心算法的基本思路
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
2.贪心算法最优性证明
2.1 贪心算法的前提
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
关键是贪心策略的选择,而贪心算法
与动态规划
的主要区别是:
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。即贪心选择是采用从顶向下
、以迭代
的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题
。
所以,贪心算法的正确性可以通过数学归纳法
或贪心交换
来给予证明。
2.2 最优子结构
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。
2.3 贪心算法与动态规划的区别
- 贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。
- 贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
- 能用贪心解决的问题,也可以用动态规划解决