贪心算法
贪心算法又叫贪婪算法,顾名思义就是问题求解时,总是做出在当前看来是最好的选择,即不保证全局最优,仅是在某种意义上的局部最优解。
贪心算法设计的关键是贪心策略的选择。
贪心算法是自顶向下计算,通过贪心选择,将原问题规约为子问题。
贪心算法具有两大基本要素:
1、贪心选择
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。(来自百度百科)
2、最优子结构
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题 。(来自百度百科)
贪心算法的基本思想及其过程:
1、建立数学模型来描述问题
2、把求解的问题分成若干个子问题
3、对每一子问题求解,得到子问题的局部最优解
4、把子问题的解局部最优解合成原来解问题的一个解
Note:对所有采用的贪心策略一定要仔细分析其是否满足“无后效性”。
注:无后效性:无后效性是指如果在某个阶段上过程的状态已知,则从此阶段以后过程的发展变化仅与此阶段的状态有关,而与过程在此阶段以前的阶段所经历过的状态无关。 利用动态规划方法求解多阶段决策过程问题,过程的状态必须具备无后效性。
贪婪算法可解决的问题通常大部分都有如下的特性(来自百度百科):
- 随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。
- 有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。
- 还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。
选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。 - 最后,目标函数给出解的值。
- 为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。
实例讲解(背包问题)
题目:圣诞老人的礼物
问题描述:
- 圣诞节来临了,在城市A中圣诞老人准备分发糖果,现在有多箱不同的糖果。
- 每箱糖果有自己的价值和重量
- 每箱糖果都可以拆分成任意散装组合带走 - 圣诞老人的驯鹿最多只能承受一定重量的糖果
- 请问圣诞老人最多能带走 多大价值 的糖果
输入:
- 第一行(由两部分组成,两个数用空格隔开):
- 糖果箱数正整数 n ( 1 <= n <= 100 )
- 驯鹿能承受的最大重量正整数 w (0 < w < 10000)
- 其余 n 行每行对应一箱糖果,(有两部分组成,两数中间用空格隔开)
- 一箱糖果的价值正整数 v
- 一箱糖果的重量正整数 w
输出:
- 输出圣诞老人能带走的糖果的最大总价值,保留一位小数
- 输出为一行,以换行符结束
输入样例:
4 15
100 4
412 8
266 7
591 2
输出样例
1193.0
解题思路:
1、 装尽可能多的糖果 → 贪心!
- 先放总重量大的糖果?
→重量大的不一定价值高 - 先放总价值高的糖果?
→总价值高的重量可能很大 - 如何贪心的选择最佳的糖果?
→选择“单位价值最大的”
2、由于每箱糖果可以任意组合,所以一定可以放入所有的糖果或者驯鹿能承受最大容量被放满
- 因此放入的总重量是确定的
→只能选择放入单位重量价值高的糖果 - 将糖果按单位重量价值从高到低排序,依次放入
→直到放满或者放完为止
实现程序代码:
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define MAXN (100+10)
//每箱糖果
struct Box{
int v, w; //价值,重量
double density; //单位重量的价值
};
int n ,w; //箱数,驯鹿承受重量
Box boxes[MAXN];
double totw, totv; //总共的价值,重量
//每个箱子按单位重量价值排序
bool operator < (const Box &a, const Box &b){
return a.density < b.density;
}
int main(){
scanf("%d%d", &n, &w);
for (int i=0; i<n; ++i){
scanf("%d%d", &boxes[i].v, &boxes[i].w);
boxes[i].density = 1.0*boxes[i].v / boxes[i].w; //计算单位重量价值
}
sort(boxes, boxes+n); //按单位重量价值排序
totw = totv = 0; //初始化
for (int i=n-1; i>=0; --i){ //按单位重量的价值从大到小依次放入
if (totw + boxes[i].w <= w){ //未放满
totw += boxes[i].w;
totv += boxes[i].v;
}
else{ //已放满
totv += boxes[i].density * (w - totw);
totw = w;
break;
}
}
printf("%.1lf\n", totv); //贪心法输出解
return 0;
}
输入:
输出:
本篇完。
上一篇文章———>动态规划进阶篇详解——习题篇