贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。

基本思路:

⒈ 建立数学模型来描述问题。

⒉ 把求解的问题分成若干个子问题。

⒊ 对每一子问题求解,得到子问题的局部最优解。

⒋ 把子问题的解局部最优解合成原来解问题的一个解。

该算法存在问题:

1. 不能保证求得的最后解是最佳的;

2. 不能用来求最大或最小解问题;

3. 只能求满足某些约束条件的可行解的范围。

实现该算法的过程:

从问题的某一初始解出发;

while 能朝给定总目标前进一步 do

求出可行解的一个解元素;

由所有解元素组合成问题的一个可行解。

贪心选择性质:

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,换句话说,当考虑做何种选择的时候,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。这是贪心算法可行的第一个基本要素。贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

最优子结构性质:

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

贪心法的一般流程

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;
例题分析        [活动安排问题] 活动安排问题是可以用贪心算法有效求解的一个很好的例子。该问题要求高效地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。

设有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)的时间将它重排。

/**
 * 活动时间安排
 */
@Test
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]);
    }
}
 
/**
 * 活动安排
 *
 * @param s 开始时间
 * @param e 结束时间
 * @return
 */
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;
}
[找零钱问题]假如老板要找给我99分钱,他有上面的面值分别为25,10,5,1的硬币数,为了找给我最少的硬币数,那么他是不是该这样找呢,先看看该找多少个25分的,诶99/25=3,好像是3个,要是4个的话,我们还得再给老板一个1分的,我不干,那么老板只能给我3个25分的拉,由于还少给我24,所以还得给我2个10分的和4个1分。

@Test
public void testGiveMoney() {
    //找零钱
    int[] m = {25, 10, 5, 1};
    int target = 99;
    int[] results = giveMoney(m, target);
    System.out.println(target + "的找钱方案:");
    for (int i = 0; i < results.length; i++) {
        System.out.println(results[i] + "枚" + m[i] + "面值");
    }
}
 
public int[] giveMoney(int[] m, int target) {
    int k = m.length;
    int[] num = new int[k];
    for (int i = 0; i < k; i++) {
        num[i] = target / m[i];
        target = target % m[i];
    }
    return num;
}
[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。 
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。 
物品 A B C D E F G 
重量 35 30 60 50 40 10 25 
价值 10 40 30 50 35 40 30 
记得当时学算法的时候,就是这个例子,可以说很经典。 
分析: 
目标函数: ∑pi最大 
约束条件是装入的物品总重量不超过背包容量,即∑wi<=M( M=150) 
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优? 
(2)每次挑选所占重量最小的物品装入是否能得到最优解? 
(3)每次选取单位重量价值最大的物品,成为解本题的策略? 
贪心算法是很常见的算法之一,这是由于它简单易行,构造贪心策略简单。但是,它需要证明后才能真正运用到题目的算法中。一般来说,贪心算法的证明围绕着整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。 
对于本例题中的3种贪心策略,都无法成立,即无法被证明,解释如下: 
(1)贪心策略:选取价值最大者。反例: 
W=30 
物品:A B C 
重量:28 12 12 
价值:30 20 20 
根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。 
(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。 
(3)贪心策略:选取单位重量价值最大的物品。反例: 
W=30 
物品:A B C 
重量:28 20 10 
价值:28 20 10 
根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。 
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。比如,求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。

[均分纸牌]有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,最后结果是对的,我们在移动过程中,只是改变了移动的顺序,而移动次数不便,因此此题使用贪心法可行的。

@Test
public void testMoveCard() {
    //总共4堆
    int heap = 4;
//        int[] cards = {9, 8, 17, 6};
    int[] cards = {10, 10, 20, 0};
    int count = moveCards(cards, heap);
    System.out.println("移动次数:" + count);
    for (int i = 0; i < cards.length; i++) {
        System.out.println("第" + (i + 1) + "堆牌数:" + cards[i]);
    }
}
 
/**
 * 均分纸牌
 * @param cards
 * @param heap
 * @return
 */
public int moveCards(int[] cards, int heap) {
    //总牌数
    int sum = 0;
    for (int i = 0; i < cards.length; i++) {
        sum += cards[i];
    }
    //每堆平均牌数
    int avg = sum / heap;
    //移动次数
    int count = 0;
    for (int i = 0; i < cards.length; i++) {
        if(cards[i] != avg) {
            int moveCards = cards[i] - avg;
            cards[i] -= moveCards;
            cards[i + 1] += moveCards;
            count++;
        }
    }
    return count;
}

利用贪心算法解题,需要解决两个问题: 
一是问题是否适合用贪心法求解。我们看一个找币的例子,如果一个货币系统有三种币值,面值分别为一角、五分和一分,求最小找币数时,可以用贪心法求解;如果将这三种币值改为一角一分、五分和一分,就不能使用贪心法求解。用贪心法解题很方便,但它的适用范围很小,判断一个问题是否适合用贪心法求解,目前还没有一个通用的方法,在信息学竞赛中,需要凭个人的经验来判断。 
二是确定了可以用贪心算法之后,如何选择一个贪心标准,才能保证得到问题的最优解。在选择贪心标准时,我们要对所选的贪心标准进行验证才能使用,不要被表面上看似正确的贪心标准所迷惑,如下面的例子。

[最大整数]设有n个正整数,将它们连接成一排,组成一个最大的多位整数。 
例如:n=3时,3个整数13,312,343,连成的最大整数为34331213。 
又如:n=4时,4个整数7,13,4,246,连成的最大整数为7424613。 
输入:n 
N个数 
输出:连成的多位数 
算法分析:此题很容易想到使用贪心法,在考试时有很多同学把整数按从大到小的顺序连接起来,测试题目的例子也都符合,但最后测试的结果却不全对。按这种标准,我们很容易找到反例:12,121应该组成12121而非12112,那么是不是相互包含的时候就从小到大呢?也不一定,如12,123就是12312而非12123,这种情况就有很多种了。是不是此题不能用贪心法呢? 
其实此题可以用贪心法来求解,只是刚才的标准不对,正确的标准是:先把整数转换成字符串,然后在比较a+b和b+a,如果a+b>=b+a,就把a排在b的前面,反之则把a排在b的后面。

@Test
public void testMaxNum() {
    //有n个正整数,将它们连接成一排,组成一个最大的多位整数
    //12112错误
    //12121正解
//        int[] nums = {12, 121};
    int[] nums = {12, 123};
    String result = maxNum(nums);
    System.out.println("组成最大整数:" + result);
}
 
/**
 * 根据给定的整数组成最大的多位数
 * @param nums
 */
public String maxNum(int[] nums) {
    String result = "";
    for (int i = 0; i < nums.length; i++) {
        String num1 = nums[i] + "";
        for (int j = 1; j < nums.length; j++) {
            String num2 = nums[j] + "";
            if ((num1 + num2).compareTo(num2 + num1) < 0) {
                int temp = nums[j];
                nums[j] = nums[i];
                nums[i] = temp;
            }
        }
    }
    for (int i = 0; i < nums.length; i++) {
        result += nums[i];
    }
    return result;
}
贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其他算法相比具有一定的速度优势。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。
————————————————
版权声明:本文为CSDN博主「别再想更好的办法」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37763204/article/details/79289532

转:五大常用算法——贪心算法详解及经典例子相关推荐

  1. 《Spring事务传播行为详解》经典例子 看完这篇,别的不用看了

    前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为.事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为.这是Spring ...

  2. 三大算法之三:贪心算法及其例题详解

    目录 零.前言 1.区分贪心算法和动态规划 1.动态规划 2.贪心算法 3.共通点 2.贪心算法得到最优解的条件 1.具有优化子结构 2.具有贪心选择性 3.任务安排问题 1.问题定义 2.优化子结构 ...

  3. 抖音推荐算法原理全文详解

    阅读目录 一.系统概览 二.内容分析 三.用户标签 四.评估分析 五.内容安全 抖音推荐算法原理全文详解 本次分享将主要介绍今日头条推荐系统概览以及内容分析.用户标签.评估分析,内容安全等原理. 回到 ...

  4. 今日头条推荐算法原理全文详解之四

    三.用户标签 内容分析和用户标签是推荐系统的两大基石.内容分析涉及到机器学习的内容多一些,相比而言,用户标签工程挑战更大. 今日头条推荐算法原理全文详解 今日头条 数据分析 产品经理 产品 好文分享 ...

  5. LPG-PCA算法实现与详解

    LPG-PCA算法实现与详解 LPG-PCA算法概要 LPG(局部像素分组) PCA(主成分分析) 算法核心思想 分阶段执行 灰度图像与彩色图像 LPG-PCA算法实现与代码详解 代码效果展示 结语 ...

  6. python如何调用文件进行换位加密_python 换位密码算法的实例详解

    python 换位密码算法的实例详解 一前言: 换位密码基本原理:先把明文按照固定长度进行分组,然后对每一组的字符进行换位操作,从而实现加密.例如,字符串"Error should neve ...

  7. DL之AlexNet:AlexNet算法的架构详解、损失函数、网络训练和学习之详细攻略

    DL之AlexNet:AlexNet算法的架构详解.损失函数.网络训练和学习之详细攻略 相关文章 Dataset:数据集集合(CV方向数据集)--常见的计算机视觉图像数据集大集合(建议收藏,持续更新) ...

  8. DL之ShuffleNet:ShuffleNet算法的架构详解

    DL之ShuffleNet:ShuffleNet算法的架构详解 相关文章 DL之ShuffleNet:ShuffleNet算法的简介(论文介绍).架构详解.案例应用等配图集合之详细攻略 DL之Shuf ...

  9. DL之MobileNetV2:MobileNetV2算法的架构详解(包括ReLu的意义)

    DL之MobileNet V2:MobileNetV2算法的架构详解 相关文章 DL之MobileNetV2:MobileNetV2算法的简介(论文介绍).架构详解.案例应用等配图集合之详细攻略 DL ...

  10. DL之SqueezeNet:SqueezeNet算法的架构详解

    DL之SqueezeNet:SqueezeNet算法的架构详解 相关文章 DL之SqueezeNet:SqueezeNet算法的简介(论文介绍).架构详解.案例应用等配图集合之详细攻略 DL之Sque ...

最新文章

  1. 20155307 2016-2017-2 《Java程序设计》第10周学习总结
  2. 攻击 FreeIPA 域:对象枚举
  3. 用预训练GNN预估点击率有奇效?
  4. linux清理swap内容,Linux如何清理swap.buffer及cache等缓存
  5. 福禄克FI-3000光纤监测显微仪使用MPO检查摄像头?
  6. 【CF1047D】Little C Loves 3 II【构造】【赛瓦维斯特定理】
  7. 素数路(prime)
  8. c++什么时候数组溢出_C语言,营养丰富的C语言五,变长数组不是动态数组
  9. php 编程祝新年快乐_第一门编程语言选什么好?
  10. 谷歌开源Allstar 项目,保护GitHub 仓库安全
  11. Mac安装svn客户端
  12. 现代密码学概论|密码学基础--仿射密码实验C语言(文末附上C语言源代码)
  13. Qt实用技巧:自定义窗口标题栏
  14. 嗨起来,让你在社交圈里有聊不完的话题
  15. android动画送礼物,Android开发仿映客送礼物效果
  16. 【车载】度(角度)和弧度的概念
  17. 【unittest学习】unittest框架主要功能
  18. Spring+mqtt 搭建物联网平台服务端
  19. suse linux关机命令,suse linux 常用命令
  20. iOS各种调试技巧豪华套餐

热门文章

  1. 我的世界java版种子多村庄_我的世界:粉丝推荐新版种子,出生附近就有11个村庄2个沙漠神殿...
  2. python利用字典破解WIFI密码
  3. 设定pic单片机端口为输入_PIC单片机入门_输入输出端口详解
  4. python与java通信——使用socket模块
  5. python基础语法篇——输入与输出
  6. 英特尔12代酷睿处理器强势来袭
  7. Python在线编程网站
  8. 一键解决WPS中的VBA支持库安装问题
  9. 【离散数学】数理逻辑 第二章 谓词逻辑(3) 谓词公式的逻辑等价与蕴含、谓词演算的永真公式
  10. python 实例化对象_python如何实例化对象