活动安排问题的 动态规划和贪心算法

这篇文章主要用来记录我对《算法导论》 贪心算法一章中的“活动选择问题”的动态规划求解和贪心算法求解 的思路和理解。

主要涉及到以下几个方面的内容:

①什么是活动选择问题—粗略提下,详细请参考《算法导论》

②活动选择问题的DP(Dynamic programming)求解–DP求解问题的思路

③活动选择问题的贪心算法求解

④为什么这个问题可以用贪心算法求解?

⑤动态规划与贪心算法的一些区别与联系

⑥活动选择问题的DP求解的JAVA语言实现以及时间复杂度分析

⑦活动选择问题的Greedy算法JAVA实现和时间复杂度分析

⑧一些有用的参考资料

①活动选择问题

给定N个活动,以及它们的开始时间和结束时间,求N个活动中,最大兼容的活动个数。比如:

活动 i: 1 2 3 4…

开始时间 si: 1 3 0 5…

结束时间 fi: 4 5 6 7…

活动1的开始时间s(1)=1,结束时间f(1)=4,它与活动2是不兼容的。因为,活动1还没有结束,活动2就开始了(s(2) < f(1))。

活动2 与 活动4 是兼容的。因为,活动2的进行区间是[3,5) 而活动4的进行区间是[5,7)

目标是:在N个活动中,找出最大兼容的活动个数。

②活动选择问题的DP(Dynamic programming)求解

1)建模

活动 i 用 a(i)来表示,开始时间用 s(i)表示,结束时间用 f(i)表示,所有活动的集合为S

定义一个合适的子问题空间,设 S(i,j) 是与 a(i) 和 a(j)兼容的活动集合。S(i,j)={a(k), a(k) belongs to S: f(i)<=s(k)<f(k)<=s(j)}

2)问题一般化(不是很理解)

这里第一个活动和最后一个活动有点特殊。为了完整表示问题,构造两个虚拟的活动: a(0) 和 a(n+1)

其中,s(0)=f(0)=0,s(n+1)=f(n+1)=Integer.MAX_VALUE

于是,S=S(0,n+1),从N个活动中找出最大兼容的活动,就转化成了求解 S(0,n+1)集合中包含的最多元素个数

3)子问题分析

假设所有的活动都按结束时间递增排序。子问题空间就是 从S(i,j)中选择最大兼容活动子集,即max{S(i,j)}

max{S(i,j)}表示与 a(i) a(j) 兼容的最大活动集合。称为为S(i,j)的解

假设 a(k)是 S(i,j)的解包含的一个活动。S(i,j)就分解为 max{S(i,k)} + max{S(k,j)}+1

从这里可以看到,将原问题分解成了两个子问题。原问题就是:求解与活动 a(i) a(j) 兼容的最大活动个数,即max{S(i,j)}

而子问题则是:max{S(i,k)} 和 max{S(k,j)}

设A(i,j)就是S(i,j)的解。那么,A(i,j)=A(i,k) U A(k,j) U {a(k)}

A(0,n+1)就是我们所求的整个问题的最优解。

4)子问题的 选择个数 分析

设c[i,j]为S(i,j)中最大兼容子集中的活动数,S(i,j)为空集时,c[i,j]=0,这是显而易见的。因为S(i,j)中都没有活动嘛,更别谈什么兼容活动了呀。

若 i>=j,c[i,j]=0。这个也很好理解,因为它不符合常识。因为,我们假设活动是以结束时间来递增排序的,在S(i,j)中,是f(i)<s(j)的。那 i 就不会大于 j

毕竟一个活动它不可能 即在 某个活动之前结束,又在该活动之后开始。哈哈。。。。。

前面提到 :假设 a(k)是 S(i,j)的解包含的一个活动。S(i,j)就分解为 max{S(i,k)} + max{S(k,j)}+1

这意味着,求S(i,j)的最优解,就需要知道 S(i,k) 和 S(k,j) 的最优解。那关键是怎么知道 S(i,k) 和 S(k,j) 的最优解呢?

答案是:一个 一个 地尝试。k 的取值范围是 (i,j),遍历(i,j)内所有的值,计算 S(i,k) 和 S(k,j)的解。就可以找到S(i,j)的最优解了。

因此,当S(i,j)不为空时,c[i,j] = max{c[i,k] + c[k,j] + 1} 其中, k belongs to (i,j) a(k) belongs to S(i,j)

下面,就是DP中的状态转移方程(递归表达式),根据它,就可以写代码实现了。

从上面分析可以看出:原问题分解成了两个子问题,要解决原问题,一共有 j-i+1中选择,然后一 一遍历求出所有的选择。这就是动态规划的特点,先分析最优子问题,然后再做选择。

③活动选择问题的贪心算法求解

所谓贪心算法,就是每次在做选择时,总是先选择具有相同特征的那个解,即“贪心”解。在这里,“贪心”的那个解则是: 结束时间最早的那个活动

具体步骤是怎样的呢?

第一步:先对活动按照结束时间进行排序。因为我们总是优先选择结束时间最早的活动的嘛。排序之后,方便选择嘛。。。

第二步:按照贪心原则 选中一个活动,然后排除 所有与该活动 有冲突的活动。

第三步:继续选择下一个活动。其实,第二步与第三步合起来就是:每次都选结束时间最早的活动,但是后面选择的活动不能与前面选择的活动有冲突。

从这里可以看出,贪心算法是在原问题上先做贪心选择,然后得到一个子问题,再求解子问题。(求解子问题的过程,就是一个不断贪心选择的过程)

④为什么这个问题可以用贪心算法求解?

看了贪心算法之后,就会有疑问?凭什么这样选就能得到最优解啊?或者说,这样做到底对不对?

别急嘛,我们可以用数学来证明这样做是正确的。而且从这个证明过程中,可以窥出动态规划与贪心算法的区别。

对于活动选择问题而言:当可用贪心算法解时,贪心的效率要比动态规划高。为什么要高呢?后面再详细讲。

这个证明具体可参考《算法导论》上的证明。它的大致证明过程就是:

当选择了贪心解时(结束时间最小的活动),也是将原问题划分成了两个子问题,但是其中一个子问题是空的,而我们只需要考虑另一个非空的子问题就可以了。

具体而言就是:假设 a(m) 是 S(i,j)中具有最早结束时间的那个活动,那按照我们的贪心选择,我们肯定会选择a(m)的嘛。选了a(m)之后,就将问题分解成了两个子问题:S(i,m) 和 S(m,j)。前面提到,活动是按结束时间排序了的,而现在a(m)又是最早结束的活动,因为,S(i,m)就是个空集,而我们只需要考虑S(m,j)

但是,这里有个重大的疑问还未解决—凭什么说 a(m) 就是 S(i,j)的最优解中的活动呢?或者说凭什么 活动m 就是最大兼容活动集合中的活动?

这里就用到经常用来证明贪心算法正确性的一个技巧—剪枝。关于这个技巧,可参考一篇博文:漫谈算法(一)如何证明贪心算法是最优

对于活动选择问题,咱就来简要证明下吧。。。其实还是《算法导论》中讲的证明,只不过我又复述一遍罢了。

慢着,我们要证明的是啥?再说一遍:凭什么说 a(m) 就是 S(i,j)的最优解中的活动呢?,我们证明的就是:a(m)是S(i,j)的最优解中的元素,即a(m)是S(i,j)最大兼容活动子集中的活动。

设A(i,j)是S(i,j)的最大兼容活动子集—也就是说,在所有与 活动a(i) 和 活动a(j) 相兼容的活动中,A(i,j)含有的活动个数最多。

将A(i,j)中的活动按结束时间递增排序。设a(k)是A(i,j)中的第一个活动。若a(k)=a(m),那没话说了。a(m)就是a(k)嘛,那a(m)肯定在A(i,j)中噻

若a(k) != a(m),这说明A(i,j)中的第一个元素(活动)不是a(m)。那我们可以运用剪枝思想,剪掉A(i,j)中的第一个活动a(k),再把活动a(m)贴到A(i,j)里面去。

这样,A(i,j)中的活动个数还是没有变化—少了个a(k),加了个a(m)啊

那么,可能你就会问了,凭什么能把 a(m)贴到 A(i,j)里面去啊?????我们可以这样想想:a(k)是A(i,j)中的第一个活动,那为什么a(k)可以在A(i,j)中呢?

废话!上面带下划线且加粗的的都说了假设 a(k)是A(i,j)中的第一个活动了啊!!

其实,这不是本质 ,本质就是:a(k)是与 a(i) 和 a(j)兼容的活动啊,而且没有和A(i,j)中的其他活动冲突啊!因为,S(i,j)的解 就是求与 a(i) 和 a(j)兼容的一组活动啊,而A(i,j)就是这样的一组活动且它是最大的(活动个数最多),能够放在A(i,j)中的活动,它一定是与a(i) 和 a(j) 兼容的。

那么,再回到a(m),a(m)同样也具有 ”本质“ 中提到的两个性质:❶a(m)是与a(i) 和 a(j) 兼容的活动 ❷a(m)没有与A(i,j)中其他活动冲突。

下面来说明下为什么 a(m)没有与A(i,j)中其他活动冲突?因为a(k)是没有与A(i,j)中的其他活动冲突的,而a(m)又是S(i,j)中结束时间最早的活动

故:,完成时间:f(m)<f(k) ,a(m)都比a(k)更早完成,而a(k)都没有与A(i,j)中的其他活动冲突,那a(m)就更不可能与A(i,j)中的其他活动冲突了。

整个思路就是:在证明中先考察一个全局最优解,然后证明可以对该解加以修改(比如运用“剪枝”技巧),使其采用贪心选择(将贪心的那个选择贴上去),这个选择将原问题变成一个相似的、但更小的问题。

终于完成了证明。好累。

⑤动态规划与贪心算法的一些区别与联系

这里只针对活动选择问题作一下比较。其他的我也不懂。

a)动态规划是先分析子问题,再做选择。而贪心算法则是先做贪心选择,做完选择后,生成了子问题,然后再去求解子问题。

b)从 a) 中可以看出,动态规划是自底向上解决问题,而贪心算法则是自顶向下解决问题。

c)动态规划每一步可能会产生多个子问题,而贪心算法每一步只会产生一个子问题。(比如这里的贪心算法产生了“二个”子问题,但是其中一个是空的。)

⑥活动选择问题的DP求解的JAVA语言实现以及时间复杂度分析

复制代码
1 /**
2 * //算法导论中活动选择问题动态规划求解
3 * @param s 活动的开始时间
4 * @param f 活动的结束时间
5 * @param n 活动数目
6 * @return 最大兼容的活动个数
7 */
8 public static int maxCompatiableActivity(int[] s, int[] f, int n){
9 int[][] c = new int[n + 2][n + 2];
10
11 for(int j = 0; j <= n+1; j++)
12 for(int i = n+1; i >= j; i–)
13 c[i][j] = 0;//if i>=j S(i,j)是空集合
14
15 int maxTemp = 0;
16 for(int j = 1; j <= n+1; j++)
17 {
18 for(int i = 0; i < j; i++)//i < j
19 {
20 for(int k = i+1; k < j; k++)// i< k <j
21 {
22 if(s[k] >= f[i] && f[k] <= s[j])//S(i,j)不空
23 {
24 if(c[i][k] + c[k][j] + 1 > maxTemp)
25 maxTemp = c[i][k] + c[k][j] + 1;
26 }
27 }//inner for
28 c[i][j] = maxTemp;
29 maxTemp = 0;
30 }//media for
31 }//outer for
32 return c[0][n+1];
33 }
复制代码
DP时间复杂度与问题的个数以及每个问题的选择数 有关。

比如这里的 S(i,j)一共大约有N^2个, 因为 1=<j<=N, 1=<i<j ,这里求和大约是 (N^2)/2(对于S(i,j) i>j没有实际意义嘛),每个S(i,j)一共有 j-i+1种 选择

故时间复杂度为O(N^3)

⑦活动选择问题的Greedy算法JAVA实现和时间复杂度分析

贪心算法即可以用递归实现,也可以用非递归实现。

复制代码
1 //贪心算法的递归解
2 public static ArrayList greedyActivitySelection(int[] s, int[] f, int i, int n, ArrayList activities){
3 //初始调用时 i = 0, 所以a(1)是必选的(注意:活动编号已经按结束时间排序)
4 int m = i + 1;
5
6 //s[m] < f[i] 意味着活动 a(m) 与 a(i)冲突了
7 while(m <= n && s[m] < f[i])
8 m++;//选择下一个活动
9
10 if(m <= n){
11 activities.add(m);
12 greedyActivitySelection(s, f, m, n, activities);
13 }
14 return activities;
15 }
16
17 //贪心算法的非递归解, assume f[] has been sorted and actId 0/n+1 is virtually added
18 public static ArrayList greedyActivitySelection2(int[] s, int[] f, int n, ArrayList acitivities){
19 //所有真正的活动(不包括 活动0和 活动n+1)中,结束时间最早的那个活动一定是最大兼容活动集合中的 活动.
20 int m = 1;
21 acitivities.add(m);
22
23 for(int actId = 2; actId <= n; actId++){
24 if(s[actId] >= f[m])//actId的开始时间在 m 号活动之后.–actId 与 m 没有冲突
25 {
26 m = actId;
27 acitivities.add(m);
28 }
29 }
30 return acitivities;
31 }
复制代码
贪心算法的时间复杂度为O(N),why?你可以看代码啊。只有一个循环啊。每个活动只会遍历一次啊。

这里从理论上来分析下:因为对于贪心算法而言,每次只有一种选择即贪心选择,而DP中每个问题S(i,j)中 j-i+1种选择。

贪心算法做出一次贪心选择后,即选中某个活动后,活动个数减少1,即问题规模减少1。

⑧参考资料

https://www.zhihu.com/question/23995189

《背包九讲》

http://www.cnblogs.com/hapjin/p/5572483.html

附完整代码:

复制代码
import java.util.ArrayList;
public class ActivitySelection {

/*** //算法导论中活动选择问题动态规划求解* @param s 活动的开始时间* @param f 活动的结束时间* @param n 活动数目* @return 最大兼容的活动个数*/
public static int maxCompatiableActivity(int[] s, int[] f, int n){int[][] c = new int[n + 2][n + 2];for(int j = 0; j <= n+1; j++)for(int i = n+1; i >= j; i--)c[i][j] = 0;//if i>=j S(i,j)是空集合int maxTemp = 0;for(int j = 1; j <= n+1; j++){for(int i = 0; i < j; i++)//i < j{for(int k = i+1; k < j; k++)// i< k <j{if(s[k] >= f[i] && f[k] <= s[j])//S(i,j)不空{if(c[i][k] + c[k][j] + 1 > maxTemp)maxTemp = c[i][k] + c[k][j] + 1;}}//inner forc[i][j] = maxTemp;maxTemp = 0;}//media for}//outer forreturn c[0][n+1];
}//贪心算法的递归解
public static ArrayList<Integer> greedyActivitySelection(int[] s, int[] f, int i, int n, ArrayList<Integer> activities){//初始调用时 i = 0, 所以a(1)是必选的(注意:活动编号已经按结束时间排序)int m = i + 1;//s[m] < f[i] 意味着活动 a(m) 与 a(i)冲突了while(m <= n && s[m] < f[i])m++;//选择下一个活动if(m <= n){activities.add(m);greedyActivitySelection(s, f, m, n, activities);}return activities;
}//贪心算法的非递归解, assume f[] has been sorted and actId 0/n+1 is virtually added
public static ArrayList<Integer> greedyActivitySelection2(int[] s, int[] f, int n, ArrayList<Integer> acitivities){//所有真正的活动(不包括 活动0和 活动n+1)中,结束时间最早的那个活动一定是最大兼容活动集合中的 活动.int m = 1;acitivities.add(m);for(int actId = 2; actId <= n; actId++){if(s[actId] >= f[m])//actId的开始时间在 m 号活动之后.--actId 与 m 没有冲突{m = actId;acitivities.add(m);}}return acitivities;
}//for test purpose
public static void main(String[] args) {//添加了 a(0) 和 a(n+1)活动. 其中s(0)=f(0)=0, s(n+1)=f(n+1)=Integer.MAX_VALUEint[] s = {0,1,3,0,5,3,5,6,8,8,2,12,Integer.MAX_VALUE};//start timeint[] f = {0,4,5,6,7,8,9,10,11,12,13,14,Integer.MAX_VALUE};//finish timeint n = 11;//活动的个数int result = maxCompatiableActivity(s, f, n);System.out.println("最大兼容活动个数: " + result);ArrayList<Integer> acts = new ArrayList<Integer>();greedyActivitySelection(s, f, 0, n, acts);for (Integer activityId : acts)System.out.print(activityId + " ");System.out.println();ArrayList<Integer> acts2 = new ArrayList<Integer>();greedyActivitySelection2(s, f, n, acts2);for (Integer activityId : acts2)System.out.print(activityId + " ");
}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/549281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vc++操作mysql数据库的技巧

(调试此Demo需要将目录里的mydb子目录拷到MySQL安装目录的data子目录下&#xff08;我的是&#xff1a;D:\Program Files\MySQL\MySQL Server 5.0\data&#xff09;摘要&#xff1a;本文详细阐述了如何进行MySQL的安装、调试&#xff0c;以及如何用VC进行编译&#xff0c;实现数…

Hibernate——(3)主键生成策略持久化类的三种状态

一 持久化类 1.持久化&#xff1a;内存对象--->数据库&#xff08;硬盘&#xff09;Hibernate持久化的框架 持久化类&#xff1a;Java对象与数据库中的表建立映射关系 Hibernate就称为持久化类&#xff08;Java类映射文件&#xff09; 2.编写规则&#xff1a; …

技术有什么用?

最近在CTO俱乐部的一个讨论分享&#xff0c;关于技术和创新的作用&#xff1a; 技术向底层延伸&#xff0c;应该是科技&#xff0c;技术向上层延伸&#xff0c;应该是应用&#xff0c;如果单纯把技术看作工具&#xff0c;编码&#xff0c;实现或一种生存的手段&#xff0c;那就…

C# 二进制BinaryFormatter进行序列化与反序列化

原文链接&#xff1a;https://blog.csdn.net/e295166319/article/details/52790131 序列化又称串行化&#xff0c;是.NET运行时环境用来支持用户定义类型的流化的机制。其目的是以某种存储形成使自定义对象持久化&#xff0c;或者将这种对象从一个地方传输到另一个地方。 .NET框…

Algorithm学习笔记 --- 迷宫问题

版权声明&#xff1a;学习交流为主&#xff0c;未经博主同意禁止转载&#xff0c;禁止用于商用。 https://blog.csdn.net/u012965373/article/details/26376987 </div><link rel"stylesheet" href"https://csdnimg.cn/release/phoenix…

Cookies和Session(二)

一、理解Session机制&#xff1a;Session是一种用于服务器端状态管理的机制&#xff0c;服务器使用一种键值对的结构来保存信息。当程序需要为某个客户端的请求创建一个Session的时候&#xff0c;服务器首先检查这个客户端的请求里是否已包含了一个Session标识 - 称为 Session …

(与运算)、|(或运算)、^(异或运算)的本质理解

按位与运算符&#xff08;&&#xff09; 参加运算的两个数据&#xff0c;按二进制位进行“与”运算。 运算规则&#xff1a;0&00; 0&10; 1&00; 1&11; 即&#xff1a;两位同时为“1”&#xff0c;结果才为“1”&#xff0c;否则为0 例如&#xff1a;3…

css资源网站收集推荐

非原创&#xff0c;来源网络。感谢原作者奉献如此精彩文章。原文地址&#xff1a;http://www.jianbitou.com/post/20_websites-learn-master-css.html1. A List Apart CSS Topics A List Apart是一个CSS优秀文章的收集网站&#xff0c;从1999年开始收集文章&#xff0c;关注最佳…

《计算机算法设计与分析》题目汇总

Github源码地址&#xff1a; https://github.com/hlk-1135/Data-Structures-and-Algorithms 递归与分治&#xff1a; 电路布线问题有重复元素的排列问题集合划分问题半数集和半数单集循环日程赛矩阵连乘问题最长公共子序列问题排列的字典序问题字典序问题 动态规划&#xff1…

教你如何窃取网络信息

随着网络业的迅速发展&#xff0c;网络安全问题日趋行严重&#xff0c;******活动日益猖獗&#xff0c;******技术成为人们关注的焦点。在因特网上&#xff0c;***站点随处可见&#xff0c;***工具可以任意下载&#xff0c;对网络的安全造成了极大的威胁。所以&#xff0c;随着…

子网划分,主机号,网络号计算

这是在做网络题时总结的一些经验和见解&#xff0c;略显粗糙&#xff0c;主要是记一下常用的ABC3类地址和相关的子网号&#xff0c;主机号的计算 ip地址 在主机或路由器中存放的IP地址&#xff08;IPv4&#xff09;都是32位的二进制代码。它包含了网络号&#xff08;net-id&…

不要用偏执毁掉一个产业

不要用偏执毁掉一个产业 近日&#xff0c;李强先生利用中科院科技政策所网络信息安全联合课题组发布的《网络信息安全形势报告》&#xff0c;又在到处散布&#xff1a;“受此影响&#xff0c;很多‘站长’重操旧业&#xff0c;大批淫秽色情手机WAP网站死灰复燃。”的信息&#…

划分子网例题详解

划分子网例题详解(网络基础考试题目)1&#xff0c;假设取得网络地址200.200.200.0 &#xff0c;子网掩码为255.255.255.0。现在一个子网有100台主机&#xff0c;另外4个子网有20台主机&#xff0c;请问如何划分子网&#xff0c;才能满足要求。请写出五个子网的子网掩码、网络地…

IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段/数字-如192.168.0.1/24是什么意思?

背景知识 IP地址 IP地址被用来给Internet上的电脑一个编号。大家日常见到的情况是每台联网的PC上都需要有IP地址&#xff0c;才能正常通信。我们可以把“个人电脑”比作“一台电话”&#xff0c;那么“IP地址”就相当于“电话号码”&#xff0c;而Internet中的路由器&#xff0…

jquery ui tabs详解(中文) 【转载】

1 属性1.11 ajaxOptions,当选项卡加载内容时&#xff0c;添加一个ajax选项。只有ajax时&#xff0c;添加的ajax选项才起作用。默认值为null。上面的例子中&#xff0c;添加了beforeSend和success两个选项。ajax还有一些选项请参考jquery ajax&#xff0c;这里不做详解。。。1.1…

数据库操作:添加、插入、更新语句

SQL常用命令使用方法&#xff1a; (1) 数据记录筛选&#xff1a; sql"select * from 数据表 where 字段名字段值 order by 字段名 [desc]" sql"select * from 数据表 where 字段名 like %字段值% order by 字段名 [desc]" sql"select top 10 * from 数…

领域驱动设计的简略设计步骤

首先&#xff0c;需要根据需求建立一个初步的领域模型&#xff0c;至少要识别出领域对象和领域对象之间的关系&#xff08;可以是没有方向的关联关系&#xff09;。这些领域对象只应该放在领域层中。如果存在应用职责&#xff0c;可以识别出应用类。它们用来协调领域对象&#…

Struts2中Action访问Servlet API的三种方法

在通常的web开发中Request和Response对象比较常见&#xff0c;但在Struts2框架中由于Action能与JSP页面进行数据交互&#xff0c;所以通常都不会用到这两个对象。如果想在Struts2程序中用到这两个对象&#xff0c;也有解决方法 Struts2的Action并未直接与任何Servlet API耦合&a…

JavaScript 经典代码大全2

1.让浏览器窗口永远都不出现滚动条。 <body style"overflow-x:hidden;overflow-y:hidden">或<body style"overflow:hidden"> 或<body scrollno> 2&#xff0c;没有水平滚动条 <body style"overflow-x:hidden"> 3&am…

思科CCNA第一学期期末考试答案

1 第 3 层头部包含的哪一项信息可帮助数据传输&#xff1f; 端口号 设备物理地址 目的主机逻辑地址 虚拟连接标识符 2 IP 依靠 OSI 哪一层的协议来确定数据包是否已丢失并请求重传&#xff1f; 应用层 表示层 会话层 传输层 3 请参见图示…