26-贪心(Greedy)

贪心(Greedy)

贪心策略:也称为贪婪差略

使用贪心策略,在执行每一步的过程中,都会选择当前状态下的最优解(局部最优解),从而希望推导出全局最优解

贪心的应用

  • 哈夫曼树
  • 最小生成树:Prim,Kruskal
  • 最短路径算法:Dijkstra

这些算法,在前面文章中也有介绍,为什么说这些算法都使用到了贪心策略呢?通过阅读完本章节内容,就会明白为什么这些算法使用到了贪心策略。

场景一:最优装载问题(加勒比海盗)

在北美东南部,有一片神秘的海域,是海盗最活跃的加勒比海

有一天,海盗们截获了一艘装满各种各样古董的货船,每一件古董都价值连城,一旦打碎,就失去了价值

现在海盗船的载重量为W,每一件古董的重量为w,海盗们该如何把尽可能多数量的古董装上海盗船?

结合上面的情景,可以理解出:每一都应该选择重量最小的古董,因为一旦选择的重量最小的古董,则海盗船剩下的载重量就会更大,就可以装载更多的古董。所以,利用贪心策略的话,应该每一次都选择重量最小的古董

根据上面的要求,现在假设海盗船的载重量W为30,w分别为3,5,4,10,7,14,2,11,利用贪心策略,每一步选择的古董重量如下

  1. 选择重量为2的古董,剩余载重量为28
  2. 选择重量为3的古董,剩余载重量为25
  3. 选择重量为4的古董,剩余重量为21
  4. 选择重量为5的古董,剩余载重量为16
  5. 选择重量为7的古董,剩余载重量为9

现在还分别剩下重量为10,11,14的古董,剩余的载重量不足以装载这些古董,所以,利用贪心算法最多能装载5个古董

前面提到,贪心策略在执行每一步的过程中,都会选择当前状态下的最优解,所以在当前问题中,就每一步都选择重量最小的古董。这就是贪心算法的一个典型应用

所以,这个问题,很容易就解出来了

public static void main(String[] args) {
    int[] weights = {3,5,4,10,7,14,2,11};
    Arrays.sort(weights);
    int capacity = 30, weight = 0, count = 0;
    for (int i = 0; i < weights.length; i++) {
        int newWeight = weight + weights[i];
        if (newWeight > capacity) break;
        weight = newWeight;
        count++;
        System.out.println(weights[i]);
    }
    System.out.println("一共选择了" + count + "件古董");
}
场景二:零钱兑换

假设有25分,10分,5分,1分的硬币,现要找给客户41分的零钱,如何办到硬币个数最少?

这里,同样可以使用到贪心的策略,在这里,优先选择面值较大的硬币,这样的话, 按道理就能做到,硬币的数量更少。所以,使用贪心策略来解决这类问题的话,就每一次都选择面值最大的硬币。所以,每一步的选择步骤如下

  1. 选择25分的硬币,剩余16分
  2. 选择10分的硬币,剩余6分
  3. 选择5分的硬币,剩余1分
  4. 选择1分的硬币,剩余0分

所以,通过上面的执行步骤,可以发现,找给客户41分零钱的话, 最少需要4个硬币。25分,10分,5分,1分各1枚。

转换为代码,结果如下

public static void main(String[] args) {
    Integer[] faces = {25,10,5,1};
    Arrays.sort(faces,(Integer f1, Integer f2) -> f2 - f1);
    int money = 41,coins = 0,i = 0;
    while (i < faces.length) {
        if (money < faces[i]) {
            i++;
            continue;
        }
        money -= faces[i];
        System.out.println(faces[i]);
        coins++;
    }
    System.out.println(coins);
}

如果,现在的零钱变为有25分,20分,5分,1分的硬币,同样要找给客户41分的零钱,上面的解决方案,每一步选择结果如下

  1. 选择25分的硬币,剩16分
  2. 选择5分,剩11分
  3. 选择5分,剩6分
  4. 选择5分,剩1分
  5. 选择1分,剩0分

可以发现,如果将零钱面值进行变化,依然按照前面的贪心策略进行解决的话,最终的解是1枚25分,3枚5分,1枚1分,一共5枚硬币。通过肉眼的观察,发现,最优的解并不是这个,可以观察出的最优解是2枚20分,1枚1分的硬币,一共3枚硬币。

所以可以发现,贪心策略在这样的问题中,是有问题的。所以存在以下问题

贪心策略并不一定能得到全局最优解

  1. 因为一般没有测试所有可能的解,容易过早做决定,所以没法达到最佳解
  2. 贪图眼前局部的利益最大化,看不到长远未来,走一步看一步

优点:简单,高效,不需要穷举所有可能,通常作为其他算法的辅助算法来使用。

缺点:鼠目寸光,不从整体上考虑其他可能,每次都采取局部的最优解,不会再回溯,因此很少情况会得到最优解

所以,现在这个问题,如果要解决的话,则需要利用到其他知识,例如动态规划。

情景三:0-1背包问题

有n件物品和一个最大承重为W的背包,没见物品的重量是w,价值是v

在保证总重量不超过W的前提下,将哪几件物品装入背包,可使得背包的总价值最大?

请注意:每一件物品只有1件,所以,每一件物品只能选择1件或者0件,因此成为0-1背包问题

在该问题中,如果采用贪心策略来解决的话,可以有3个不同的方案

  1. 价值主导:优选择价值最高的物品放进背包(这样可以使得背包的物品价值的最大化)
  2. 重量主导:优先选择重量最轻的物品放进背包(这样可以放进更多的物品)
  3. 价值密度主导:优先选择价值密度最高的物品放进背包(价值密度 = 价值 ÷ 重量)

可以发现,这三种策略都有一定的道理,那到底哪一种方案才是正确的呢?不过先不用考虑哪一种方案是正确的,首先从这里就可以观察出,贪心策略是不靠谱的,因为感觉利用贪心策略,都可以达到目的。所以再一次证明了,贪心策略是贪图眼前的利益最大化,不会去考虑未来长远的问题。

背包问题实例

假设现在背包的最大承重量为150,7个物品如表格所示

现在就结合物品的信息,来对前面的3种不同方案进行检测

  1. 价值主导:背包中放入的物品编号为4,2,6,5,总重量为130,总价值为165
  2. 重量主导:背包中放入的物品编号为6,7,2,1,5,总重量为140,总价值为155
  3. 价值密度主导:放入背包中的物品编号为6,2,7,4,1,总重量为150,总价值为170

所以,这个问题,转化为代码如下

public static void main(String[] args) {

    //价值主导
    select("价值主导",(Article a1,Article a2) -> {
        return a2.value - a1.value;
    });
    //重量主导
    select("重量主导",(Article a1,Article a2) -> {
        return a1.weight - a2.weight;
    });
    //价值密度主导
    select("价值密度主导",(Article a1,Article a2) -> {
        return Double.compare(a2.valueDensity,a1.valueDensity);
    });

}

static void select(String title, Comparator<Article> cmp) {
    Article[] articles = new Article[] {
            new Article(35,10),new Article(30,40),
            new Article(60,30),new Article(50,50),
            new Article(40,35),new Article(10,40),
            new Article(25,30)
    };
    Arrays.sort(articles,cmp);

    int capacity = 150, weight = 0, value = 0;
    List<Article> selectedArticles = new LinkedList<>();
    for (int i = 0; i < articles.length && weight < capacity; i++) {
        int newWeight = weight + articles[i].weight;
        if (newWeight <= capacity) {
            weight = newWeight;
            value += articles[i].value;
            selectedArticles.add(articles[i]);
        }
    }
    System.out.println("【" + title + "】");
    System.out.println("【总价值】 : " + value);
    for (int i = 0; i < selectedArticles.size();i++) {
        System.out.println(selectedArticles.get(i));
    }
}

所以,通过以上的几个场景,就知道了贪心策略的一些特性,贪图眼前局部的利益最大化,每一步都会选择当前状态下的最优解,这就是贪心。

demo下载地址

完!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容