贪心算法

原理

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

特性

贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。能够用贪心算法求解的问题一般具有两个重要特性:贪心选择性质和最优子结构性质。

1、贪心选择性质

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素。贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。证明的大致过程为:首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始。做了贪心选择后,原问题简化为规模更小的类似子问题。然后用数学归纳法证明通过每一步做贪心选择,最终可得到问题的整体最优解。其中,证明贪心选择后的问题简化为规模更小的类似子问题的关键在于利用该问题的最优子结构性质。

2、最优子结构性质

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。

3、贪心算法与动态规划算法的差异

动态规划和贪心算法都是一种递推算法,均有最优子结构性质,通过局部最优解来推导全局最优解。两者之间的区别在于:贪心算法中作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留,贪心算法每一步的最优解一定包含上一步的最优解。动态规划算法中全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解。

4、基本思路:

  1. 建立数学模型来描述问题。
  2. 把求解的问题分成若干个子问题。
  3. 对每一子问题求解,得到子问题的局部最优解。
  4. 把子问题的解局部最优解合成原来解问题的一个解。

活动安排问题

活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合,是可以用贪心算法有效求解的很好例子。该问题要求高效地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。

问题描述

设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。

求解思路

将活动按照结束时间进行从小到大排序。然后用i代表第i个活动,s[i]代表第i个活动开始时间,f[i]代表第i个活动的结束时间。按照从小到大排序,挑选出结束时间尽量早的活动,并且满足后一个活动的起始时间晚于前一个活动的结束时间,全部找出这些活动就是最大的相容活动子集合。事实上系统一次检查活动i是否与当前已选择的所有活动相容。若相容活动i加入已选择活动的集合中,否则,不选择活动i,而继续下一活动与集合A中活动的相容性。若活动i与之相容,则i成为最近加入集合A的活动,并取代活动j的位置。

下面给出求解活动安排问题的贪心算法,各活动的起始时间和结束时间存储于数组s和f中,且按结束时间的非减序排列。如果所给的活动未按此序排列,可以用O(nlogn)的时间重排。具体代码如下:


#include "stdafx.h"
#include <iostream>
using namespace std; template<class Type>
void GreedySelector(int n, Type s[], Type f[], bool A[]);const int N = 11;int main()
{//下标从1开始,存储活动开始时间int s[] = {0,1,3,0,5,3,5,6,8,8,2,12};//下标从1开始,存储活动结束时间int f[] = {0,4,5,6,7,8,9,10,11,12,13,14};bool A[N+1];cout<<"各活动的开始时间,结束时间分别为:"<<endl;for(int i=1;i<=N;i++){cout<<"["<<i<<"]:"<<"("<<s[i]<<","<<f[i]<<")"<<endl;}GreedySelector(N,s,f,A);cout<<"最大相容活动子集为:"<<endl;for(int i=1;i<=N;i++){if(A[i]){cout<<"["<<i<<"]:"<<"("<<s[i]<<","<<f[i]<<")"<<endl;}}return 0;
}template<class Type>
void GreedySelector(int n, Type s[], Type f[], bool A[])
{A[1]=true;int j=1;//记录最近一次加入A中的活动for (int i=2;i<=n;i++)//依次检查活动i是否与当前已选择的活动相容{if (s[i]>=f[j]){ A[i]=true;j=i;}else{A[i]=false;}}
}

由于输入的活动以其完成时间的非减序排列,所以算法greedySelector每次总是选择具有最早完成时间的相容活动加入集合A中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。算法greedySelector的效率极高。当输入的活动已按结束时间的非减序排列,算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。

例:设待安排的11个活动的开始时间和结束时间按结束时间的非减序排列如下:

算法greedySelector 的计算过程如下图所示。图中每行相应于算法的一次迭代。阴影长条表示的活动是已选入集合A的活动,而空白长条表示的活动是当前正在检查相容性的活动。

若被检查的活动i的开始时间Si小于最近选择的活动j的结束时间fi,则不选择活动i,否则选择活动i加入集合A中。贪心算法并不总能求得问题的整体最优解。但对于活动安排问题,贪心算法greedySelector却总能求得的整体最优解,即它最终所确定的相容活动集合A的规模最大。这个结论可以用数学归纳法证明。

证明如下:设E={0,1,2,…,n-1}为所给的活动集合。由于E中活动安排安结束时间的非减序排列,所以活动0具有最早完成时间。首先证明活动安排问题有一个最优解以贪心选择开始,即该最优解中包含活动0.设a是所给的活动安排问题的一个最优解,且a中活动也按结束时间非减序排列,a中的第一个活动是活动k。如k=0,则a就是一个以贪心选择开始的最优解。若k>0,则我们设b=a-{k}∪{0}。由于end[0] ≤end[k],且a中活动是互为相容的,故b中的活动也是互为相容的。又由于b中的活动个数与a中活动个数相同,且a是最优的,故b也是最优的。也就是说b是一个以贪心选择活动0开始的最优活动安排。因此,证明了总存在一个以贪心选择开始的最优活动安排方案,也就是算法具有贪心选择性质。

例题练手

题目地址:点击打开链接

题目描述

有若干个活动,第i个开始时间和结束时间是[Si,fi),只有一个教室,活动之间不能交叠,求最多安排多少个活动?

问题分析

  1. 开始最早的活动优先,目标是想尽早结束活动,让出教室。

然而, 这个显然不行,因为最早的活动可能很长,影响我们进行后面的活动。例如活动开始和结束时间分别为[0, 100), [1,2) ,[2, 3), [3, 4),[4,5],安排[0,100)的这个活动之后,其他活动无法安排,可是最优解是安排除它外的4个活动。

  1. 短活动优先, 目标也是尽量空出教室。但是不难构造如下反例: [0,5) [5,10) [3, 7), 这里[3,7)最短,但如果我们安排了[3,7),其它两个无法安排了。但是最优解显然是安排其它两个,而放弃[3,7),可见这个贪心策略也是不行的。
  2. 最少冲突的活动优先, 既然上面安排活动是想减少冲突,那么如果我们优先安排冲突最少的活动可以么?至少从(1)和(2)看来,这个策略是有效的。真是对的么? 尝试这个例子:
    [0,2) [2,4) [4,6) [6,8)
    [1,3) [1,3) [1,3) [3,5) [5,7) [5,7) [5,7)

    看一下[0,2) 和3个活动冲突——3个[1,3)

[2,4)和4个活动冲突3个[1,3)和一个[3,5)
[4,6)和也和4个活动冲突3个[5,7)和一个[3,5)
[6,8)和3个活动冲突——3个[5,7)

下面[1,3)和[5,7)每个都和5个活动冲突,
而[3,5)只和两个活动冲突——[2,4)和[4,6)。

那按照我们的策略应该先安排[3,5), 可是一旦选择了[3,5),我们最多只可能安排3个活动。
但明显第一行的4个活动都可以安排下来,所以这种策略也是不对的。

  1. 看似最不对的策略——结束时间越早的活动优先。这个策略是有效的,我们可以证明。假设最优解OPT中安排了m个活动,我们把这些活动也按照结束时间由小到大排序,显然是不冲突的。假设排好顺序后,这些活动是a(1) , a(2), a(3)….am

假设按照我们的贪心策略,选出的活动自然是按照结束时间排好顺序的,并且也都是不冲突的,这些活动是b(1), b(2) …b(n)

问题关键是,假设a(1) = b(1), a(2) = b(2)…. a(k) = b(k),但是a(k+1) != b(k+1),回答几个问题:

  1. b(k+1)会在a(k+2), a(k+3), …. a(m)中出现么?
    不会。因为b(k+1)的结束时间是最早的,即f(b(k+1)) <= f(a(k+1)),而a(k+2), a(k+3), …. a(m)的开始时间和结束时间都在f(a(k+1))之后,所以b(k+1)不在其中。
  2. b(k+1)和a(1), a(2), …. a(k) 冲突么?
    不冲突,因为a(1), a(2), …. a(k)就是b(1), b(2), …. b(k)
  3. b(k+1)和a(k+2), a(k+3), …. a(m)冲突么?
    不冲突,因为f(b(k+1)) <= f(a(k+1)),而a(k+2), a(k+3), …. a(m)的开始时间都在f(a(k+1))之后,更在f(b(k+1))之后。

因此我们可以把a(k+1) 换成b(k+1), 从而最优解和我们贪心得到的解多了一个相同的,经过一个一个替换,我们可以把最优解完全替换成我们贪心策略得到的解。 从而证明了这个贪心策略的最优性。
最后,我们来提供输入输出数据,由你来写一段程序,实现这个算法,只有写出了正确的程序,才能继续后面的课程。

输入

第1行:1个数N,线段的数量(2 <= N <= 10000)
第2 - N + 1行:每行2个数,线段的起点和终点(-10^9 <= S,E <= 10^9)

输出

输出最多可以选择的线段数量。

输入示例

3
1 5
2 3
3 6

输出示例

2

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Node{int start;int end;
} a[10001];
/**自定义的排序规则
*/
bool cmp(Node x,Node y){if(x.end<y.end)return true;else if(x.end==y.end&&x.start>y.start)return true;return false;
}
int main(){int n;cin>>n;for(int i=0;i<n;i++){cin>>a[i].start>>a[i].end;}//a是待排序的数组的首地址,a+n是尾地址//cmp是自定义排序规则sort(a,a+n,cmp);int ans =0;int end = -1000000000;for(int i=0;i<n;i++){if(a[i].start>=end){ans++;end=a[i].end;}}cout<<ans<<endl;return 0;
}

参考文档

贪心算法与活动安排问题
ACM--贪心算法--活动安排问题

算法笔记(0002) - 【贪心算法】活动安排问题相关推荐

  1. 贪心法——活动安排问题

    贪心法--活动安排问题 贪心法 贪心法的本质可以认为是动态规划在特定条件下的优化.贪心法满足两个性质,最优子结构性质和贪心选择性质. 满足最优子结构性质意味着问题可以被层层分解为子问题,这些子问题构成 ...

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

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

  3. python贪心算法最短路径_贪心算法---最短路径问题

    一,贪心算法的设计思想 • 从问题的某一个初始解出发逐步逼近给定的目标,每一步都作一个不可回溯的决策,尽可能地求得最好的解.当达到某算法中的某一步不需要再继续前进时,算法停止. 二,贪心算法的基本性质 ...

  4. 【数据结构与算法】【算法思想】贪心算法

    贪心算法 回溯算法 分治算法 动态规划 四种基本的算法思想:贪心算法,分治算法,回溯算法,动态规划,他们不是具体算法,常用来指导我们设计具体的算法和编码等. 一:贪心算法有很多经典应用 霍夫曼编码(H ...

  5. 【算法学习】贪心算法

    参考算导第三版第16章 贪心算法 文章目录 1. 活动选择问题 1.1 活动选择问题的最优子结构 1.2 贪心选择 1.3 递归贪心算法 1.4 迭代贪心算法 2. 贪心算法原理 2.1 贪心选择性质 ...

  6. 算法笔记之hoorspool算法

    算法笔记之hoorspool算法 从右往左进行字符扫描,如果所有匹配成功,则找到了匹配的字串,如果遇到不匹配的时候,就需要将模式右移动,这个时候考虑的是文本与模式最后一个字符对齐的文本字符C 当字符不 ...

  7. 分治算法,动态规划算法和贪心算法的区别和联系

    分治算法,动态规划算法和贪心算法的区别和联系 (一)分治算法 分治算法为什么叫分治算法? 分治这个名字可以分成两部: 第一部分是分,表示把一个原问题分解成很多个小问题,逐个解决; 第二部分是治, 表示 ...

  8. 【算法专题】贪心算法的介绍及使用场景

    文章目录 一.什么是贪心算法 二.贪心算法的应用场景 2.1 分糖果 2.2 找零钱 2.3 区间覆盖 三.贪心算法的使用总结

  9. 贪心算法最短路径java_贪心算法-单源最短路径

    算法思想:贪心算法 实际问题:单源最短路径 编程语言:Java 问题描述 单源最短路径算法,又称迪杰斯特拉算法.其目的是寻找从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题. 算法构 ...

  10. C++编程笔记:贪心算法实现活动安排问题

    问题描述: 设有n个活动的集合E={1,2,-,n},其中,每个活动都要求使用同一资源,而在同一时间内只有一个活动能使用这一资源.每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且s ...

最新文章

  1. 面试小结之Elasticsearch篇
  2. 爬墙技术哪家强,师范找锡伟
  3. webpack基础概念
  4. 再来一次的C语言贪吃蛇小游戏(三)
  5. 一文读懂汇编程序的多个分段的程序--详解
  6. python显示range_python中range如何输出
  7. 通过OData创建C4C Lead时,遇到Account missing的错误消息
  8. mysql不能删除外键吗,MySQL不能删除外键约束所需的索引
  9. 权限申请_Android 开发工程师必须掌握的动态权限申请,三步轻松搞定!
  10. SSM后台框架下配合实现小程序图片上传至阿里云OOS
  11. 番外篇:根据学习程度划分程序员的级别
  12. Excel实验情况对比排序
  13. Https如何做到通信安全
  14. JavaScript实现(轮播图,随机数,抽名字游戏,广告循环文字)
  15. 揭秘刷百度权重那些不为人知的事情
  16. 万网域名注册促销活动 - 阿里云域名优惠口令免费获取
  17. 一个页面中有多个audio标签,其中一个播放结束后自动播放下一个,audio连续播放
  18. 广州如何下载公积金的缴交证明和个人信息表
  19. R pdf大小_免费的pdf压缩大法
  20. STM32 HAL库之IIC

热门文章

  1. 2011年8月编程语言排行榜
  2. 深入解读神策分析云两大闭环,助力企业全面数字化转型应用
  3. 用python实现简单飞行棋
  4. 国产麒麟操作系统调用PageOffice在线编辑保存WPS文档
  5. 在Linux中查找文件系统类型的7种方法(ext2,ext3或ext4)
  6. Linux下安装kettle
  7. 菜鸟教程离线PDF下载地址整理
  8. IBM FileNet P8 的权限管理
  9. linux下强制卸载命令,强制删除rpm包的方法
  10. networkx学习(五)无标度网络