贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
1、建立数学模型来描述问题。
2、把求解的问题分成若干个子问题。
3、对每一子问题求解,得到子问题的局部最优解。
4、把子问题的解局部最优解合成原来解问题的一个解。
指所求问题的整体最优解可以通过一系列局部最优的选择,换句话说,当考虑做何种选择的时候,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。这是贪心算法可行的第一个基本要素。贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。
- Greedy(C) //C是问题的输入集合即候选集合
- {
- S={ }; //初始解集合为空集
- while (not solution(S)) //集合S没有构成问题的一个解
- {
- x=select(C); //在候选集合C中做贪心选择
- if feasible(S, x) //判断集合S中加入x后的解是否可行
- S=S+{x};
- C=C-{x};
- }
- return S;
- }
[找零钱问题]美国面额硬币有:1,5,10,25;我们给36美分的零钱,看能得怎样的结果?
- function MinCoinChange(coins) {
- var coins = coins;
- var cache = {};
- this.makeChange = function (amount) {
- var change = [], total = 0;
- for (var i = coins.length; i >= 0; i--) {
- var coin = coins[i];
- while (total + coin <= amount) {
- change.push(coin);
- total += coin;
- }
- }
- return change;
- }
- }
- var minCoinChange = new MinCoinChange([1, 5, 10, 25]);
- var change = minCoinChange.makeChange(36);
- console.log(change)
[背包问题]
有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

用贪心算法解背包问题的基本步骤:首先计算每种物品单位重量的价值v[i]/w[i],然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。
- function backpage(){
- var Weight = [35,30,60,50,40,10,25];
- var Value = [10,40,30,50,35,40,30];
-
- var array = new Array();
- for(var i=0; i<7; i++) //将每件物品重量、价值封装到对象中,放入数组
- {
- var obj = {
- value : Value[i],
- weight : Weight[i],
- char_mark : String.fromCharCode(65+i), //物品名称
- mark : false,
- ratio : (Value[i] / Weight[i]).toFixed(2)//物品的价值比
- }
- array.push(obj);
- }
-
- var str = ""
- for(var i=0;i<7;i++)
- str += array[i].ratio+"\t";
- console.log("价值比:",str);
-
- var weight_all = 0.0; //总重量
- var value_all = 0.0; //总价值
- var max = 0.0;
- var charArray = []; //放入物品的顺序表
- var flag,n = 0;
- var w = ""; //拼接物品重量
- while(weight_all <= 150)
- {
- var max = array[0].ratio
- for(var index=1;index < 7; index++)
- {
- if(array[index].ratio > max && array[index].mark === false)
- {
- max = array[index].ratio ;
- flag = index;
- }
- }
-
- charArray[n++] = array[flag].char_mark;
- array[flag].mark = true;
- weight_all += array[flag].weight;
- w += array[flag].weight+ "\t";
- value_all += array[flag].value;
- max = 0.0;
- }
-
- str = ""
- for(var j=0;j<n-1;j++)
- str += charArray[j] + "\t";
- console.log("物品顺序:",str);
- console.log("物品重量:",w);
- console.log("weight_all:",weight_all - array[flag].weight); //总重量减去超出的重量
- console.log("value_all:",value_all);
- }
-
- backpage()
[活动安排问题]
设有n个活动的集合e={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si< fi。如果选择了活动i,则它在半开时间区间[si,fi]内占用资源。若区间[si,fi]与区间[sj,fj]不相交,则称活动i与活动j是相容的。也就是说,当si≥fi或sj≥fj时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
在下面所给出的解活动安排问题的贪心算法gpeedyselector中,各活动的起始时间和结束时间存储于数组s和f{中且按结束时间的非减序:.f1≤f2≤…≤fn排列。如果所给出的活动未按此序排列,我们可以用o(nlogn)的时间将它重排。
- function testArrangeActivity() {
- var start = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12];
- var end = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
- var results = arrangeActivity(start, end);
- for (var i = 0; i < results.length; i++) {
- var index = results[i];
- console.log("开始时间:" + start[index] + ",结束时间:" + end[index]);
- }
- }
- /**
- * 活动安排
- * @param s 开始时间
- * @param e 结束时间
- * @return
- */
- function arrangeActivity(s, e) {
- var total = s.length;
- var endFlag = e[0];
- var results = [];
- results.push(0);
- for (var i = 0; i < total; i++) {
- if (s[i] > endFlag) {
- results.push(i);
- endFlag = e[i];
- }
- }
- return results;
- }
-
- testArrangeActivity()
[均分纸牌]
有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.可以在任一堆上取若干张纸牌,然后移动。
移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6 移动三次可以达到目的:从③取4张牌放到④ 再从③区3张放到②然后从②去1张放到①。
输入输出样例:4
9 8 17 6
屏幕显示:3
算法分析:设a[i]为第I堆纸牌的张数(0<=I<=n),v为均分后每堆纸牌的张数,s为最小移动次数。
我们用贪心算法,按照从左到右的顺序移动纸牌。如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种情况移动:
1、若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆;
2、若a[i]< v,则将v-a[i]张从第I+1堆移动到第I堆。为了设计的方便,我们把这两种情况统一看作是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v; a[I+1]=a[I+1]+a[i]-v.
在从第I+1堆取出纸牌补充第I堆的过程中可能会出现第I+1堆的纸牌小于零的情况。
如n=3,三堆指派数为1 2 27 ,这时v=10,为了使第一堆为10,要从第二堆移9张到第一堆,而第二堆只有2张可以移,这是不是意味着刚才使用贪心法是错误的呢?
我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张,第二堆剩下-7张,在从第三堆移动17张到第二堆,刚好三堆纸牌都是10,最后结果是对的,我们在移动过程中,只是改变了移动的顺序,而移动次数不便,因此此题使用贪心法可行的。
- /**
- * 均分纸牌
- * @param cards
- * @param heap
- * @return
- */
- function moveCards(cards, heap) {
- //总牌数
- var sum = 0;
- for (var i = 0; i < cards.length; i++) {
- sum += cards[i];
- }
- //每堆平均牌数
- var avg = sum / heap;
- //移动次数
- var count = 0;
- for (var j = 0; j < cards.length; j++) {
- if(cards[j] != avg) {
- var moveCards = cards[j] - avg;
- cards[j] -= moveCards;
- cards[j + 1] += moveCards;
- count++;
- }
- }
- return count;
- }
-
- function testMoveCard() {
- //总共4堆
- var heap = 4;
- var cards = [9, 8, 17, 6];
- var count = moveCards(cards, heap);
- console.log("移动次数:" + count);
- for (var i = 0; i < cards.length; i++) {
- console.log("第" + (i + 1) + "堆牌数:" + cards[i]);
- }
- }
-
- testMoveCard()
[最大整数]
设有n个正整数,将它们连接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343,连成的最大整数为34331213。
又如:n=4时,4个整数7,13,4,246,连成的最大整数为7424613。
输入:n
N个数
输出:连成的多位数
算法分析:先把整数转换成字符串,然后在比较a+b和b+a,如果a+b>=b+a,就把a排在b的前面,反之则把a排在b的后面。
- /**
- * 根据给定的整数组成最大的多位数
- * @param nums
- */
- function maxNum(nums) {
- var result = "";
- for (var i = 0; i < nums.length; i++) {
- var num1 = nums[i] + ""; //转换为字符串
- for (var j = 1; j < nums.length; j++) {
- var num2 = nums[j] + "";
- if ((num1 + num2)<(num2 + num1)) {
- var temp = nums[j];
- nums[j] = nums[i];
- nums[i] = temp;
- }
- }
- }
- for (var i = 0; i < nums.length; i++) {
- result += nums[i];
- }
- return result;
- }
-
- function testMaxNum() {
- //有n个正整数,将它们连接成一排,组成一个最大的多位整数
- //12112错误
- //12121正解
- var nums = [12, 121];
- //var nums = [12, 123];
- var result = maxNum(nums);
- console.log("组成最大整数:" + result);
- }
-
- testMaxNum()