在学习数据结构的时候,我们已经见过了贪心思想在Prim和Kruskal中的完美应用,贪心思想因为其的简洁在算法中经常会被用到,有的时候在生活中,我们也会无意中使用到l贪心算法。比如在去shopping时,经常需要进行找零钱的过程,我们总是不自觉的先把大的找出来。
那么什么是贪心思想?
贪心
贪心算法总是作出在当前看来最好的选择,也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。
只有在满足最优子结构的情况下贪心算法得到的结果才是最优结果。
比如找钱的问题,我要给你一百,那么我尽可能每一次给你最多的。
或者比如磁盘的最优存储问题,所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
Prim和kruskal算法都是每次选择最小的边纳入生成树。
从许多可以用贪心算法求解的问题中看到这类问题一般具有两个重要的性质:贪心选择性质和最优子结构性质。
所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这也是贪心问题和动态规划问题的主要区别。
贪心算法的问题
在n行m列的正整数矩阵中,要求从每一行中选一个数,使得选出的n个数的和最大。
可运用贪心策略,选n次,每一次选相应行中的最大值即可。
但是,在一个n*m的方格阵中,每一格子赋予一个数,规定每次移动时只能向上或向右,现试找出一条路径,使其从左下角至右上角所经过的权值之和最大。
同样考虑贪心策略,从左下角向右上角移动,每次移动选择权值较大的一个方向。
3 4 6
1 2 100
以2*3矩阵为例,采用贪心的策略得到的是1,3,4,6和为14但是实际的最优结果为1,2,100,6和为109.
所以说贪心算法并不是总是可行,证明当前问题存在贪心选择性质(全局最优解可以通过局部最优贪心选择达到)和最优子结构性质(问题的最优解包含了其子问题的最优解)。所以贪心问题如果当前的选择不会干扰之后的选择,则不会出现问题。
其他的情况就需要进行证明,证明的最好办法就是将最小子问题进行一步步的合并,直到最后还原为最后的原问题,若所得到的解是总体最优的则可以使用贪心思想,否则不可以。
比如上面的问题,我们的走一步的最优解为1,3,然后我们判断一次走两步的最优解是否任然为1,3这个路径,答案显然不是,变为 1,2,100这个路径,所以显然不能使用贪心思想。
算法实例
假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。
public static void main(String[] args) {
int[] coin = {100,50,10,5,1};
Main main = new Main();
int target = 800;
int ci = main.findMoney(target,coin);
System.out.println(ci);
}
public int findMoney(int target,int[] coinType){
int count = 0;
for(int i =0;i<coinType.length;i++){
if(target>=coinType[i]){
int num = target / coinType[i];
target -= num*coinType[i];
count += num;
}
}
return count;
}
有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi 。一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,ai和aj两个活动就可以被安排在这一天。该问题就是要安排这些活动使得尽量多的活动能不冲突的举行。
public void testArrangeActivity() {
int[] start = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
int[] end = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
List<Integer> results = arrangeActivity(start, end);
for (int i = 0; i < results.size(); i++) {
int index = results.get(i);
System.out.println("开始时间:" + start[index] + ",结束时间:" + end[index]);
}
}
public List<Integer> arrangeActivity(int[] s, int[] e) {
int total = s.length;
int endFlag = e[0];
List<Integer> results = new ArrayList<>();
results.add(0);
for (int i = 0; i < total; i++) {
if (s[i] > endFlag) {
results.add(i);
endFlag = e[i];
}
}
return results;
}
部分背包问题, 有n个物体,第i个物体重量为wi,价值为vi,在总重量不超过C的情况下,让总价值尽可能的高。每个物体都可以只取一部分。
我们可以考虑重量和价值的比值作为单价。