力扣刷题系列——贪心思想I

贪心算法及经典例题(一)

以下内容参考于:https://www.jianshu.com/p/d69229f0bc99 与 https://blog.csdn.net/qq_15054345/article/details/89514163,仅作为个人日后复习查阅。

贪心

贪心算法总是作出在当前看来最好的选择,也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。

只有在满足最优子结构的情况下贪心算法得到的结果才是最优结果。

比如找钱的问题,我要给你一百,那么我尽可能每一次给你最多的。

或者比如磁盘的最优存储问题,所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。

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)候选集合C:为了构造问题的解决方案,有一个候选集合C作为问题的可能解,即问题的最终解均取自于候选集合C。例如在找零钱问题中,各种面值的货币构成候选集合。

(2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成一个满足问题的完整解。例如在找零钱问题中,已付出的货币构成解集合。

3)可行解函数solution:检查解集合S是否构成问题的一个可行解。例如,在找零钱问题中,可行解函数是已付出的货币金额恰好等于应找零钱。
(4)选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。例如,在找零钱问题中,贪心策略就是在候选集合中选择面值最大的货币。
(5)约束函数constraint:检查解集合中加入一个候选对象是否满足约束条件。例如,在找零钱问题中,约束函数是每一步选择的货币和已付出的货币相加不超过应找零钱。

贪心算法的一般框架:
Greedy(C) //C是问题的输入集合即候选集合
{
    S={ }; //初始解集合为空集
    while (not solution(S)) //集合S没有构成问题的一个可行解
    {
      x=select(C); //在候选集合C中做贪心选择
      if constraint(S, x) //判断集合S中加入x后是否满足约束条件
           S=S+{x};
           C=C-{x};
     }
     return S;
}

基本思想:

从问题的某一个初始解出发,通过一系列的贪心选择——当前状态下的局部最优选择,逐步逼近给定的目标,尽可能快地求得更好的解。
在贪心算法(greedy method)中采用逐步构造最优解的方法。在每个阶段,都作出一个按某个评价函数最优的决策,该最优评价函数称为贪心准则(greedy criterion)。
贪心算法的正确性,就是要证明按贪心准则求得的解是全局最优解。

基本步骤:

①决定问题的最优子结构
②设计出一个递归解
③证明在递归的任一阶段,最优选择之一总是贪心选择。那么,做贪心选择总是安全的。
④证明通过做贪心选择,所有子问题(除一个以外)都为空。
⑤设计出一个实现贪心策略的递归算法。
⑥将递归算法转换成迭代算法。

  动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征

贪心算法和动态规划算法都要求问题具有最优子结构性质,这是2类算法的一个共同点。

算法实例

假设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的情况下,让总价值尽可能的高。每个物体都可以只取一部分。我们可以考虑重量和价值的比值作为单价。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值