在贪心算法之前总是先考虑动态规划,然后再进一步证明能否用贪心算法来解决此问题。贪心算法是一种有效的方法,适用于一大类问题。例如,最小生成树(prim、kruskal),Dijkstra最短路径算法以及哈弗曼编码。最小生成树是贪心的一个经典例子。
动态规划算法,每一步都要做出选择,但是这些选择依赖于子问题的解。因此解决动态规划问题依赖于子问题的解。因此动态规划一般是自底向上的(当然可以转换为递归,变成自顶向下),从小子问题处理至大子问题。贪心算法中,一种自顶向下的递归策略(当然了贪心问题可以转换为迭代问题),我们每一步做出当前最佳的选择(贪心选择),然后将当前最佳问题缩小到子问题的最佳问题。
贪心算法和动态算法都利用了最优子结构。那么决定问题的最优子结构就是解决问题的关键之所在了。
贪心算法的关键步骤:
(1)决定问题的最优子结构;(2)设计出一个递归解;(3)证明递归的任一阶段,最优的选择总是贪心选择,即做出贪心选择以后所有其他子问题都为空。(4)将递归转化为迭代。
1. 0、1背包问题和部分背包问题
0、1背包问题涉及到一个物品要么放要么不放;而部分背包问题是说一个物品我可以带走它的一部分。
两个背包问题都利用了最优子结构,但是部分背包问题却可用贪心算法来解,而0、1背包只能用动态规划问题来解。为解决部分背包问题,先对每个物品求出“元/kg”,就是求出物品的一个单价。这样,我们在放物品的时候首先考虑贵的物品,然后让贵的物品尽量多放,贵的物品考虑完了,考虑第二贵的物品,一直下去直到背包装满。这样我们首先就可以先给按照物品的单价进行o(nlgn)的一个排序,然后依次选择,这就是部分背包问题的贪心解。但是0、1背包问题如果按照同样的思维来考虑不行,因为0、1背包问题是一个物品要么放要么不放。那么就存在这样一个问题,单价最大的物品放进去以后没有把背包填满,此时其他物品又放不进来,很可能如果我放两件其他物品加起来的价格比放单价最大的物品要高,问题就出在这里。
2.给你一个非负数整数n,判断n是不是一些数(这些数不允许重复使用,且为正数)的阶乘之和,如9=1!+2!+3!,如果是,则输出Yes,否则输出No;(出自:http://blog.csdn.net/niushuai666/article/details/6417913)
解题思路:
1.先求得最接近n的阶乘。
2.每次找到最接近n的阶乘后,n = n - a[i]. 之后重复查找最接近n的阶乘数(关键步骤)
3.若n == 0 则n可分解为阶乘之和,反之不能。
#include<iostream>
using namespace std;
int main()
{
int k[9] = {1,2,6,24,120,720,5040,40320,362880};
bool flag = false;
int m;
cin>>m; //要判断的数
for(int i = 8; i >= 0; --i)
{
if(m >= k[i] && m > 0)
m -= k[i];
if(m == 0)
flag = true;
}
if(flag)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
3.天黑后,N个人要过河,只有一个蜡烛且只有一条船,船每次最多坐2个人。不管怎样,过河者(1个人或者2个人)都必须有蜡烛,所以过河后可能需要人返回送蜡烛,然后再继续过河。问怎样过河时间最短。(http://blog.csdn.net/niushuai666/article/details/6398224)
思路:
贪心思想(一般都是先排序)
关键步骤:每次从此岸到彼岸移动的两个人要么这两个人中有一个是最快的那个人,要么这两个人到达彼岸后再也不回来。即:要么最快+最慢,要么最慢+次慢。
1.对N个人过河时间从小到大排序。speed[i]
2.分情况讨论:
⑴当n = 1,直接过河。sum = speed[0]
(2)当n = 2,直接过河。 sum = speed[1]
(3)当n = 3,无论怎么过河, sum = speed[0] + speed[1] + speed[2]
(4)当n = 4,设从小到大排序后位a,b,c,d
用最小的来送:b + a + c + a + d = 2a + b + c + d(a,b过去,a回来,a,c过去,a回来,a,d过去)
两小送两大:b + a + d + b + b = a + 3b + d(a,b过去,a回来,c,d过去,b回来,a,b过去)
sum = min(2a + b + c + d, a + 3b + d)
(5)当n > 4,设从小到大排序后位a,b,……,c,d,大于4个人,目标就是把最大的两个人送过去。
用最小的来送:d + a + c + a = 2a + c + d(a,d过去,a回来,a,c过去,a回来)
两小送两大: b + a + d + b = a + 2b + d(a,b过去,a回来,c,d过去,b回来)
循环:sum = min(2a + b + c + d, a + 3b + d),直到n <= 4时候结束。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int speed[1010];
int main()
{
int n, sum = 0;
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &speed[i]);
sort(speed, speed + n); //从小到大排序
while(n)
{
if(n == 1)
{
sum += speed[0];
break;
}
else if(n == 2)
{
sum += speed[1];
break;
}
else if(n == 3)
{
sum += speed[0] + speed[1] + speed[2];
break;
}
else if(n == 4)
{
if(speed[2] + speed[0] - 2 * speed[1] <= 0)
sum += (2 * speed [0] + speed[1] + speed[2] + speed[3]);
else
sum += (speed[0] + 3 * speed[1] + speed[3]);
break;
}
else
{
if(speed[n - 2] - 2 * speed[1] + speed[0] <= 0)
sum += (speed[n - 1] + speed[n - 2] + 2 * speed[0]);
else
sum += (speed[n - 1] + 2 * speed[1] + speed[0]);
n -= 2;
}
}
printf("%d\n", sum);
return 0;
}
4.多处理器调度问题:某工厂有n个独立的作业,由m台相同的机器进行加工处理。作业i所需的加工时间为ti,任何作业在被处理时不能中断,也不能进行拆分处理。现厂长请你给他写一个程序:算出n个作业由m台机器加工处理的最短时间。(http://blog.csdn.net/niushuai666/article/details/6408264)
解题思路:(贪心算法)
采用最长处理时间作业优先的贪心算则策略设计出解多机调度问题的较好的近似算法。
1.当n<=m时,只要将作业时间区间分配给作业即可。时间为最长时间的作业。
2.当n>m时,首先将n个作业从大到小排序。然后依此顺序分步将作业分配给空闲的处理机,每步分配给一个机器,而且需要考虑分配哪一个作业。根据这种思想可利用如下贪心原则:从剩下的作业中,选择需要处理时间最长。然后依次选择处理时间次长的作业,直到所有的作业全部处理完毕,或者机器不能再处理其他作业。
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int speed[10010];
int mintime[101];
bool cmp( const int &x, const int &y )
{
return x > y;
}
int main()
{
int m, n;
memset(speed, 0, sizeof(speed));
memset(mintime, 0, sizeof(mintime));
cin>>n>>m;
for(int i = 0; i < n; ++i)
{
cin>>speed[i];
}
sort(speed, speed + n, cmp);
for(int i = 0; i < n; ++i) //同时满足n <= m和n > m
{
*min_element(mintime, mintime + m) += speed[i]; //当mac >= task时,复制 mac < task时,最小元素累加(贪心倒罗)
}
cout<<*max_element(mintime, mintime + m)<<endl; //最大的即为总时间
return 0;
}
5.键盘输入一个高精度的正整数N(此整数中没有‘0’),去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的N和S,寻找一种方案使得剩下的数字组成的新数最小。 输出应包括所去掉的数字的位置和组成的新的正整数。(N不超过240位) (http://blog.csdn.net/niushuai666/article/details/6372856)
思路:(典型的贪心策略,方法就是从简单入手,慢慢复杂。从n=1开始推导就会发现规律)
现在假设有一个数,124682385,假如n = 1,则结果为12462385。n = 2,结果为1242385。可以知道:最优解是删除出现的第一个左边>右边的数,因为删除之后高位减小,很容易想...那全局最优解也就是这个了,因为删除S个数字就相当于执行了S次删除一个数,因为留下的数总是当前最优解...
这样,就可以很容易写出来了。。。。。。