一、 贪心策略的定义
【定义1】 贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。
其实,从"贪心策略"一词我们便可以看出,贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解。
二、贪心算法的特点
通过上文的介绍,可能有人会问:贪心算法有什么样的特点呢?我认为,适用于贪心算法解决的问题应具有以下2个特点:
1、贪心选择性质:
所谓贪心选择性质是指应用同一规则f,将原问题变为一个相似的、但规模更小的子问题、而后的每一步都是当前看似最佳的选择。这种选择依赖于已做出的选择,但不依赖于未做出的选择。从全局来看,运用贪心策略解决的问题在程序的运行过程中无回溯过程。关于贪心选择性质,读者可在后文给出的贪心策略状态空间图中得到深刻地体会。
2、局部最优解:
我们通过特点2向大家介绍了贪心策略的数学描述。由于运用贪心策略解题在每一次都取得了最优解,但能够保证局部最优解得不一定是贪心算法。如大家所熟悉得动态规划算法就可以满足局部最优解,在广度优先搜索(BFS)中的解题过程亦可以满足局部最优解。
在遇到具体问题时,往往分不清哪些题该用贪心策略求解,哪些题该用动态规划法求解。在此,我们对两种解题策略进行比较。
三、 贪心策略的理论基础 -- 矩阵胚
正如前文所说的那样,贪心策略是最接近人类认知思维的一种解题策略。但是,越是显而易见的方法往往越难以证明。下面我们就来介绍贪心策略的理论--矩阵胚。
"矩阵胚"理论是一种能够确定贪心策略何时能够产生最优解的理论,虽然这套理论还很不完善,但在求解最优化问题时发挥着越来越重要的作用。
【定义 [B]3】 矩阵胚是一个序对M=[S,I] ,其中S是一个有序非空集合,I是S的一个非空子集,成为S的一个独立子集。[/B]
如果 M是一个N×M的矩阵的话,即:
若M是无向图G的矩阵胚的话,则S为图的边集,I是所有构成森林的一组边的子集。
如果对S的每一个元素X(X∈S)赋予一个正的权值W(X),则称矩阵胚M=(S,I)为一个加权矩阵胚。
适宜于用贪心策略来求解的许多问题都可以归结为在加权矩阵胚中找一个具有最大权值的独立子集的问题,即给定一个加权矩阵胚,M=(S,I),若能找出一个独立且具有最大可能权值的子集A,且A不被M中比它更大的独立子集所包含,那么A为最优子集,也是一个最大的独立子集。
矩阵胚理论对于我们判断贪心策略是否适用于某一复杂问题是十分有效的。
四、 几种典型的贪心算法
贪心策略在图论中有着极其重要的应用。诸如哈夫曼编码、Kruskal、 Prim、 Dijkstra等体现“贪心”思想的图形算法更是广泛地应用于树与图的处理。
五、 贪心策略在P类问题求解中的应用
在现实世界中,我们可以将问题分为两大类。其中一类被称为P类问题,它存在有效算法,可求得最优解;另一类问题被称为NPC类问题,这类问题到目前为止人们尚未找到求得最优解的有效算法,这就需要每一位程序设计人员根据自己对题目的理解设计出求较优解的方法。下面我们着重分析贪心策略在求解P类问题中的应用。
在现实生活中,P类问题是十分有限的,而NPC类问题则是普遍的、广泛的。
[ 例 1] 删数问题
试题描述 键盘输入一个高精度的正整数N(不超过240位),去掉其中任意S个数字后剩下的数字按左右次序组成一个新的正整数。对给定的N和S,寻找一种删数规则使得剩下得数字组成的新数最小。
试题背景 此题出自NOI94
试题分析 这是一道运用贪心策略求解的典型问题。此题所需处理的数据从表面上看是一个整数。其实,大家通过对此题得深入分析便知:本题所给出的高精度正整数在具体做题时将它看作由若干个数字所组成的一串数,这是求解本题的一个重要突破。这样便建立起了贪心策略的数学描述。
每次删除一个数字,选择一个使剩下的数最小的数字作为删除对象,之所以选择这样”贪心”的操作,是因为删S个数字的全局最优解包含了删一个数字的子问题的最优解.
当S=1时,在N中删除哪一个数字能达到最小的目的?从左到右每相邻的两个数字比较:若出现左边大于右边,则删除左边的大数字.若不出现降序排列,即所有数字全部升序,则删除最右边的大数字.
当S>1,按上述操作一个一个删除,删除一个达到最小后,再从头即从串首开始,删除第2个,依次分解为S次完成.
若删除不到S个后已无左边大于右边的减序,则停止删除操作,打印剩下串的左边L-S个数字即可(相当于删除了若干个最右边的大数字,这里L为原数字N的位数).
附源程序 :
#include
#include
using namespace std;
int main()
{
string n;
int s,i,x,l,m;
while(cin>>n>>s)
{
i=-1,m=0,x=0;
l=n.length();
while(x
{
i++;
if(n[i]>n[i+1])//出现递减,删除递减的首数字
{
n=n.erase(i,1);
x++;// x统计删除数字的个数
i=-1;//从头开始查递减区间
}
if(i==l-x-2&&x
m=1;//已经无递减区间,m=1脱离循环
}
cout<<n.substr(0,l-s+x);//只打印剩下的左边l-(s-x)个数字
}
}
[ 例 2] 1098 均分纸牌noip2002
http://codevs.cn/problem/1098/
题目描述 Description
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。 例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10) ->从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
输入描述 InputDescription
第一行N(N 堆纸牌,1 <= N <= 100)
第二行A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出描述 OutputDescription
输出至屏幕。格式为:
所有堆均达到相等时的最少移动次数。‘
样例输入 SampleInput
4
9 8 17 6
样例输出 SampleOutput
3
试题分析 假设纸牌数量可以是负数
1)对于最左边的纸牌,为了使它的纸牌数达到平均,只要还没有达到平均无论其余子情况如何移动,一定有一步是把自己多余的纸牌移动到右边,或者是从右边移动进来自己差了多少张纸牌
2)第一堆牌只有和右边进行交互是合法的,步骤1是必须的
3)处理好第一堆后,其余操作一定不涉及第一堆,否则答案更劣(经过前一堆是没有意义的)
4)无视第一堆,于是现在又是情况1了(子结构)
5)对于一个会出现负数的方案,通过调整移动顺序,一定可以转变为一个不出现负数的方案。
作者:Yangff链接:http://www.zhihu.com/question/27883948/answer/38487509来源:知乎
从左向右考虑,每堆纸牌有三种状态:
1) arr[i] == 平均值,考虑arr[i+1]
2) arr[i] < 平均值,此时由arr[i+1]移给arr[i]纸牌。 => 移动纸牌数:(平均值 - arr[i])张
注:
考虑此时,arr[i]缺牌,那么i右边的牌堆必然多牌,无论哪堆多牌,必然有由arr[i+1]将牌移给arr[i]的过程。
3) arr[i] > 平均值,此时由arr[i]移给arr[i+1]纸牌。 => 移动纸牌书:(arr[i] - 平均值)张
注:
考虑此时,arr[i]多牌,那么i右边的牌堆必然缺牌,无论哪堆缺牌,必然有由arr[i]将牌移给arr[i+1]的过程。每个状态下,所做的动作都是必然需要的,没有做无用功,所以结果最优。
作者:Jming S链接://www.zhihu.com/question/27883948/answer/64933786来源:知乎
[ 例3]最优乘车问题
http://codevs.cn/problem/1722/
试题描述 H城是一个旅游胜地,每年都有成千上万的人前来观光.为方便游客,巴士公司在各个旅游景点及宾馆、饭店等地都设置了巴士站,并开通了一些单向巴士线路。每条单向巴士线路从某个巴士站出发,依次途径若干个巴士站,最终到达终点巴士站。
阿昌最近到H城旅游,住在CUP饭店。他很想去S公园游玩。听人说,从CUP饭店到S公园可能有也可能没有直通巴士。如果没有,就要换乘不同线路的单向巴士,还有可能无法乘巴士到达。
现在用整数1,2,...,n给H城的所有巴士站编号,约定CUP饭店的巴士站编号为1,S公园巴士站的编号为N。
写一个程序,帮助阿昌寻找一个最优乘车方案,使他在从CUP饭店到S公园的过程中换车的次数最少。
试题背景 出自NOI97
试题分析 此题看上去很像一道搜索问题。在搜索问题中,我们所求的使经过车站数最少的方案,而本题所求解的使换车次数最少的方案。这两种情况的解是否完全相同呢?我们来看一个实例:
如图5所示:共有5个车站(分别为a、b、c、d、e), 共有3条巴士线(线路A:a→d;线路B:a→b→c→e;线路C:d→e)。此时要使换车次数最少,应乘坐线路B的巴士,路线为:a→b→c→e,换车次数为0;要使途经车站数最少,乘坐线路应为a→d→e,换车次数为1。所以说使换车次数最少的路线和使途经车站数最少的方案不一定相同。这使不能用搜索发求解此问题的原因之一。
原因之二,来自对数学模型的分析。我们根据题中所给数据来建立一个图后会发现该图中存在大量的环,因而不适合用搜索法求解。
其实,此题完全可以套用上文所提到的Dijkstra算法来求解。每l两个顶点的路径长度为1,最少的换乘次数就是最短路了。
练习:
Codevs 1128 导弹拦截2010年NOIP全国联赛普及组
Codevs1198 国王游戏2012年NOIP全国联赛提高组
Codevs1723 最佳游览1997年NOI全国竞赛
http://codevs.cn/problem/1723/
首先需要注意的是这道题,样例错了、题目描述错了,数据输入的应该是 m,n+1
样例应该是。
3 6
-50 -47 36 -30 -23
17 -19 -34 -13 -8
-42 -3 -43 34 -45
[ 试题描述] 某旅游区的街道成网格状(见图),其中东西向的街道都是旅游街,南北向的街道都是林荫道。由于游客众多,旅游街被规定为单行道。游客在旅游街上只能从西向东走,在林荫道上既可以由南向北走,也可以从北向南走。
阿隆想到这个旅游区游玩。他的好友阿福给了他一些建议,用分值表示所有旅游街相邻两个路口之间的道路值得浏览得程度,分值从-100到100的整数,所有林荫道不打分。所有分值不可能全是负值。
例如下图是被打过分的某旅游区的街道图:
阿隆可以从任一路口开始浏览,在任一路口结束浏览。请你写一个程序,帮助阿隆寻找一条最佳的浏览路线,使得这条路线的所有分值总和最大。
试题背景 这道题同样出自NOI97,'97国际大学生程序设计竞赛的第二题(吉尔的又一个骑车问题)与本题是属于本质相同的题目。
试题分析 由于林荫道不打分,也就是说,无论游客在林荫道中怎么走,都不会影响得分。因题可知,若游客需经过某一列的旅游街,则他一定要经过这一列的M条旅游街中分值最大的一条,才会使他所经路线的总分值最大。这是一种贪心策略。贪心策略的目的是降维,使题目所给出的一个矩阵便为一个数列。下一步便是如何对这个数列进行处理。在这一步,很多人用动态规划法求解,这种算法的时间复杂度为O(n ),当林荫道较多时,效率明显下降。其实在这一步我们同样可以采用贪心法求解。这时的时间复杂度为O(n)。