【算法学习笔记】25.贪心法 均分纸牌问题的分析
贪心法:
贪⼼算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪⼼心策略的选择,选择的贪⼼策略必须具备⽆后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
太概念化了。总结起来三点:
可行性:必须满足问题的约束
局部最优:当前步骤中所有可行的选择里最佳的局部选择。
不可取消:选择一旦做出,后面的步骤就无法改变。
问题要具有贪心选择性:问题的最优解可以通过一系列的局部最优选择来达到。(最重要的一步,决定这个问题是否可以用贪心法来解决,此处的解决特指找到最优解)。
最优子结构性质:指一个问题的最优解一定要包含子问题的最优解。
贪心和DP的差别在哪呢,首先他萌确实都有最优子结构的性质,但是DP通常是以自底向上的方式解决各个子问题(如22中的整装待发问题就是从底部的每一层逐渐建立起那个二维数组),而贪心的方法通常是自顶向上的。
均分纸牌问题的分析:
均分纸牌问题:有 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)。
先放代码在分析吧,代码比较短。
int main(){ int N; int pokers[MAX]; cin>>N; int total = 0; for (int i=0; i<N; i++) { cin>>pokers[i]; total+=pokers[i]; } int avg = total/N,times=0; for(int i=0;i<N;i++){ if(pokers[i]!=avg){ pokers[i+1] -= avg - pokers[i]; times++; } } cout<<times<<endl; }
可以看到最核心的那个循环的思想是这样的:
从第一堆牌开始处理,如果第一堆牌整好是avg那么就放在一边不管了。
如果第一堆牌不是avg,那么就要把第二堆牌(合法的移动只有从2移到1,这也是这个算法的精髓之处)移动几张到第一堆,恰好使第一堆等于avg,从而只考虑第二堆开始到第N堆为止这些堆如何搞的子问题。然后依次递归下去。
这里的一个小技巧是认为牌数可以为负数,这样才能继续下去。综上,这个步骤是合理的。但是看不出来是最优的。可见,贪心法确实是比较容易实现,因为比较符合人类直觉,但是不好证明。
再反过来看一下前面提到的几点,可行性满足,不可取消,每一次操作都是直接赋值,局部最优,当前情况下,只能从右往左移动,且贪心地想尽快让第一堆满足约束。
至于为什么是最优解,(最少的步骤),要看这个问题到底是不是具有贪心选择性的。也就是看是不是全局最优解是由局部最优解产生的。对于这个事情,需要严格的数学证明才行。
http://www.zhihu.com/question/27883948 在知乎上问了这个问题。