算法导论 第十六章:贪心算法之单任务调度问题

贪心算法是使所做的选择看起来都是当前最优的,通过所做的局部最优选择来产生一个全局最优解。

其具有的性质如下:

1)贪心选择性质:一个全局最优解可以通过局部最优(贪心)选择来达到。即,在考虑如何做选择时,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。

这一点是贪心算法不同于动态规划之处:在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。因此,解动态规划问题一般是自底向上,从小问题处理至大问题。在贪心算法中,我们所做的总是当前看似最优的选择,然后再解决选择之后所出现的子问题。贪心算法所做的当前选择可能依赖于已经做出的所有选择,但是不依赖于有待做出的选择或子问题的解,因此,贪心算法通常采用自顶向下的方式,一个一个地做出贪心选择,不断将给定的问题实例归约为更小的问题。

2)最优子结构:如果一个问题的一个最优解包含其子结构的最优解,则称该问题具有最优子结构。

贪心算法和动态规划都利用了最优子结构性质,通常在用贪心算法求解问题时,能够给出一个动态规划解;但是能用动态规划解的的问题不一定能够用贪心算法求解。

贪心算法设计步骤:

1)  觉得问题的最优子结构

2)  设计一个递归解

3)  证明在递归解的任一阶段,最优选择之一总是贪心选择。

4)  证明通过贪心选择,所有子问题(除一个以外)都为空

5)  设计出一个实现贪心策略的递归算法

6)  将递归算法转换成迭代算法

活动选择问题

假设有一个集合S={a(1),a(2),...,a(n)}, 每个活动i有一个开始时间s(i)和一个截止时间f(i),满足 0 ≤ s(i)< f(i) < ∞,若 s(i) ≥ f(j) 或 s(j) ≥ f(i),则活动a(i) 和a(j)兼容。

活动选择问题就是选出一个由互相兼容的问题组成的一个最大子集合。

EG:

一个活动集合如下:

其最大子集有:{a(2),a(4),a(9),a(11)},{a(1),a(4),a(8),a(11)}等

分析:

1)最优子结构

定义集合S(i,j)表示每个活动在活动a(i)结束之后开始,活动a(j)开始之前结束的集合:

另外定义子问题的最大活动集为A(i,j),则有:

设c[i,j]为S(i,j)中最大兼容子集中的活动数。则有:

2)贪心选择:

设任意的非空子集S(k),令a(m)是集合S(k)中具有最早结束时间的一个活动,则a(m)包含在最大兼容活动子集中。

求解:

1)递归贪心算法

伪代码如下:

当初始时活动的结束时间有序,则该算法运行时间为:Θ(n)

2)迭代贪心算法

伪代码如下:

当初始时活动的结束时间有序,则该算法运行时间为:Θ(n)

迭代贪心算法实现的活动选择问题完整代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdlib>
using namespace std;

typedef struct Activity{
	int s;
	int f;
	}Activity;

bool a_less_b(const Activity &a,const Activity &b)
{
	return a.f<b.f;
	}

vector< vector<int> > GA_ActivitySelect(Activity *a,int n)
{
	vector< vector<int> > A;    //store the solutions
	vector<Activity> act;

	for(int i=0;i<n;i++)
		act.push_back(a[i]);
	sort(act.begin(),act.end(),a_less_b);   //sort finish time with increasing

	for(int i=0;i<n;i++){
		vector<int> temp;
		int k=i;
		temp.push_back(k+1);                 //subscript starts 0
		for(int m=k+1; m<n ; m++)
			if(act[m].s >= act[k].f){
				temp.push_back(m+1);
				k=m;
				}
		A.push_back(temp);
		if(temp.size()!=A[0].size())
			A.erase(A.end()-1);
		}
	return A;

	}
void Print_Optimal(vector< vector<int> > r)
{

	for(int i=0;i<r.size();i++)
	{
		vector<int> temp;
		temp=r[i];
		cout<<"The solution "<<i+1<<" :";
		for(int j=0;j<temp.size();j++)
			cout<<temp[j]<<"   ";
		cout<<endl;
		}
	}
int main()
{
	Activity a[]={{1,4},{3,5},{0,6},{5,7},{3,9},{5,9},{6,10},{8,11},{8,12},{2,14},{12,16}};
	int n=sizeof(a)/sizeof(Activity);

	vector< vector<int> > res;          //maybe have many solutions
	res=GA_ActivitySelect(a,n);
	cout<<"The laregest activity set is:"<<endl;
	Print_Optimal(res);
	return 0;
	}

运行结果:

该程序找出可能的解,但并没有找出所有满足要求的解。

【注:若有错误,请指正~~~】

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-03 23:59:15

算法导论 第十六章:贪心算法之单任务调度问题的相关文章

算法导论笔记——第十六章 贪心算法

通常用于最优化问题,我们做出一组选择来达到最优解.每步都追求局部最优.对很多问题都能求得最优解,而且速度比动态规划方法快得多. 16.1 活动选择问题 按结束时间排序,然后选择兼容活动. 定理16.1 考虑任意非空子问题Sk,令am是Sk中结束时间最早的活动,则am在Sk的某个最大兼容活动子集中. 16.2 贪心算法原理 设计贪心算法步骤: 1>将最优化问题转化为这样的形式:对其做出一次选择后,只剩下一个子问题需要求解. 2>证明作出贪心选择后,原问题总是存在最优解,即贪心选择总是安全的. 3

第十六章 贪心算法——0/1背包问题

1.问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题. 2.最优性原理: 设(y1,y2,…,yn)是 (3.4.1)的一个最优解.则(y2,…,yn)是下面相应子问题的一个最优解:

算法导论第十二章__二叉搜索数

package I第12章__二叉搜索树; //普通二叉树 public class BinaryTree<T> { // -----------------------数据结构--------------------------------- private int height = 0; private Node<T> rootNode; class Node<T> { T t; int key; Node left; Node right; public Node

算法导论第十五章动态规划

概述: 1.动态规划是通过组合子问题的解而解决原问题的. 2.动态规划适用于子问题不是独立的情况,也就是各子问题的包含公共的子子问题. 3.动态规划对每个子问题只求解一次,将其结果保存在一张表中. 4.动态规划的设计步骤:a.描述最优解的结构b.递归定义最优解的值c.按自底向上的方式计算最优觖的值d.由计算出的结构构造一个最优解 15.1钢条切割 钢条切割问题:给定定长的钢条和价格表,求切割方案,使得收益最大.如果n英寸的钢条的价格足够大,则不需要切割. 代码如下: //朴素递归求解钢条切割收益

算法导论第十九章 斐波那契堆

<算法导论>第二版中在讨论斐波那契堆之前还讨论了二项堆,但是第三版中已经把这块的内容放到思考题中,究极原因我想大概是二项堆只是个引子,目的是为了引出斐波那契堆,便于理解,而且许多经典的算法实现都是基于斐波那契堆,譬如计算最小生成树问题和寻找单源最短路径问题等,此时再把二项堆单独作为一章来讲显然没有必要.类似的堆结构还有很多,如左倾堆,斜堆,二项堆等,下次我打算开一篇博客来记录下它们的异同点. 一.摊还分析(第十七章) 这些高级的数据结构的性能分析一般是基于一个技术——摊还分析,可以理解成一种时

算法导论第十二章 二叉搜索树

一.二叉搜索树概览 二叉搜索树(又名二叉查找树.二叉排序树)是一种可提供良好搜寻效率的树形结构,支持动态集合操作,所谓动态集合操作,就是Search.Maximum.Minimum.Insert.Delete等操作,二叉搜索树可以保证这些操作在对数时间内完成.当然,在最坏情况下,即所有节点形成一种链式树结构,则需要O(n)时间.这就说明,针对这些动态集合操作,二叉搜索树还有改进的空间,即确保最坏情况下所有操作在对数时间内完成.这样的改进结构有AVL(Adelson-Velskii-Landis)

《算法导论》第六章 练习题 Exercise

6.1-1 在高度为 h 的堆中,元素最多有 2h+1 - 1 个,最少有 2h  个.注意算法导论里的高度是指深度,从 0 开始而不是从 1 开始. 6.1-2 这很好想,但是不好证明. 由已知高度为 h 的堆,它的元素个数满足 2h   <= n <= 2h+1 - 1 ,解出 lg(n+1) - 1 <= h <= lgn ,但是它不够"合理",因为当 n = 2h+1-1 时,n 等于 2的幂 - 1,此时 lg(n+1) -1 = ?lgn? ,所以 

【算法导论】第六章、堆排序

基本过程: 1.保持最大堆的性质:假设两个子堆都满足,只需要根节点依次换下去,复杂度O(lg n) 2.初始化堆:后半段都是叶子,在前半段从后往前,依次执行上述最大堆性质的操作,名义复杂度是O(n lg n),但是有更精确的计算, 在高度为h的节点为O(h), 因此为 n\sigma (h / 2^h),其复杂度为O(n).(思想是高层复杂度才高,指数衰减,而复杂度增长是lg级别,因此被dominate掉了) 堆排序算法:先建最大堆,每次把顶上的位置与合适的位置互换,然后执行过程1, 共执行n次

算法导论 第二十二章:图的搜索

图有两种标准的表示方法,即邻接矩阵和邻接表(通常邻接矩阵用于稠密图,邻接表用于稀疏图).如下: 对于图的搜索有两种方法:深度优先搜索 & 广度优先搜索. 广度优先搜索(Breadth-first search) 广度优先搜索是将已发现和未发现顶点之间的边界沿其广度方向向外扩展.亦即算法首先会发现和s距离为k的所有点,然后才会发现和s距离为k+1的其他顶点. 伪代码: EG: 运行时间:O(V+E). 深度优先遍历(Depth-first search) 在深度优先搜索中,对于最新发现的顶点,如果