算法导论-16.1-4 活动教室选择问题

本文介绍了一种使用贪心算法解决活动调度问题的方法,通过排序和优先队列维护,实现O(nlgn)时间复杂度的高效解决方案。

题目:

假设要用很多个教室对一组活动进行调剂。我们希望应用尽可能少的教室来调剂所有的活动。请给出一个有效的贪心算法,来断定哪一个活动应应用哪一个教室。
(这个题目也被成为区间图着色(interval-graph coloring)题目。我们可作出一个区间图,其顶点为已知的活动,其边连接着不兼容的活动。为使任两个相邻结点的色彩均不雷同,所需的起码色彩对应于找出调剂给定的所有活动所需的起码教室数。)

思考:

常规算法:

针对所有活动,先用16.1中的方法选择一个教室安排活动,然后对剩余的活动再选择一个新的教室,依次这样安排,直到活动全部安排完。

这个算法的时间复杂度是O(n^2),稍换一个角度,就能得到一个O(nlgn)的算法

O(nlgn)算法:

针对一个特定的活动,为它选择一个适合的教室。对所有已经选择过的教室,用一个最大堆的优先队列(存储活动的开始时间)来维护,比较的依据是在这个教室中最早开始的活动的开始时间

具体步骤是这样的:

step1:对所有活动按结束时间从小到大排序。

step2:从最后一个活动开始,向第一个活动,依次针对每个活动做以下处理

(1)获取堆顶元素的信息

(2)如果堆顶的活动开始时间早于当前活动的结束时间,则申请一个新的教室,把活动的开始时间填入堆中

(3)如果堆顶的活动开始时间晚于当前活动的结束时间,删除堆顶,并就把当前活动填入堆中。//此时即是每个教室活动的最优选择

(4)选择下一个活动,直到所有活动都处理过

代码:O(lgn)

1、堆的相关操作:

//Heap.h
#include <iostream>
#include <stdio.h>
using namespace std;

#define PARENT(i) (i)>>1
#define LEFT(i) (i)<<1
#define RIGHT(i) ((i)<<1)+1

int length = 0;//数组中元素的个数
int heap_size = 0;//属于堆的元素个数,看到HeapSort就会明白

/*************************以下是堆处理函数****************************************/
//使以i结点为根结点的子树成为堆,调用条件是确定i的左右子树已经是堆,时间是O(lgn)
//递归方法
void Max_Heapify(int *A, int i)
{
	int l = LEFT(i), r = RIGHT(i), largest;
	//选择i、i的左、i的右三个结点中值最大的结点
	if(l <= heap_size && A[l] > A[i])
		largest = l;
	else largest = i;
	if(r <= heap_size && A[r] > A[largest])
		largest = r;
	//如果根最大,已经满足堆的条件,函数停止
	//否则
	if(largest != i)
	{
		//根与值最大的结点交互
		swap(A[i], A[largest]);
		//交换可能破坏子树的堆,重新调整子树
		Max_Heapify(A, largest);
	}
}
/**********************以下是优先队列处理函数****************************************/
//将元素i的关键字增加到key,要求key>=A[i]
void Heap_Increase_Key(int *A, int i, int key)
{
	if(key < A[i])
	{
		cout<<"new key is smaller than current key"<<endl;
		exit(0);
	}
	A[i] = key;
	//跟父比较,若A[PARENT(i)]<A[i],则交换
	//若运行到某个结点时A[PARENT(i)]>A[i],就跳出循环
	while(A[PARENT(i)] > 0 && A[PARENT(i)] < A[i])
	{
		swap(A[PARENT(i)], A[i]);
		i = PARENT(i);
	}
}
//把key插入到集合A中
void Max_Heap_Insert(int *A, int key)
{
	if(heap_size == 99)
	{
		cout<<"heap is full"<<endl;
		exit(0);
	}
	heap_size++;length++;
	A[heap_size] = -0x7fffffff;
	Heap_Increase_Key(A, heap_size, key);
}
//返回A中最大关键字,时间O(1)
int Heap_Maximum(int *A)
{
	return A[1];
}
//去掉并返回A中最大关键字,时间O(lgn)
int Heap_Extract_Max(int *A)
{
	if(heap_size < 1)
	{
		cout<<"heap underflow"<<endl;
		exit(0);
	}
	//取出最大值
	int max = A[1];
	//将最后一个元素补到最大值的位置
	A[1] = A[heap_size];
	heap_size--;
	//重新调整根结点,维护堆的性质
	Max_Heapify(A, 1);
	//返回最大值
	return max;
}
//删除堆中第i个元素
void Heap_Delete(int *A, int i)
{
	if(i > heap_size)
	{
		cout<<"there's no node i"<<endl;
		exit(0);
	}
	//把最后一个元素补到第i个元素的位置
	int key = A[heap_size];
	heap_size--;
	//如果新值比原A[i]大,则向上调整
	if(key > A[i])
		Heap_Increase_Key(A, i, key);
	else//否则,向下调整
	{
		A[i] = key;
		Max_Heapify(A, i);
	}
}
2、贪心算法:

//main.cpp
#include <iostream>
#include "Heap.h"
using namespace std;

#define N 11
//用于存储每个活动的信息
struct node
{
	int id;//记录它是第几个活动
	int start;//开始时间
	int finish;//结束时间
}A[N+1];
//用于排序
bool cmp(node a, node b)
{
	return a.finish < b.finish;
}
//最大堆
int H[N+1];
//O(lgn)贪心算法
void Greedy()
{
	//对所有活动的结束时间从小到大排序
	sort(A+1, A+N+1, cmp);
	int i, ret = 0;
	//从最后一个活动开始,到第一个活动,依次针对每个活动做以下处理
	for(i = N; i >= 1; i--)
	{
		//1)获取堆顶元素的信息(4)更新堆(5)选择下一个活动,直到所有活动都处理过
		int temp = Heap_Maximum(H);
		//(2)如果堆顶的活动开始时间早于当前活动的结束时间,则:
		if(temp < A[i].finish)
		{
			//申请一个新的教室
			ret++;
			//把活动的开始时间填入其中
			Max_Heap_Insert(H, A[i].start);
		}
		//(3)如果堆顶的活动开始时间晚于当前活动的结束时间,则:
		else
		{
			//删除堆顶,并插入新元素
			Heap_Extract_Max(H);
			Max_Heap_Insert(H, A[i].start);
		}
		//选择下一个活动,直到所有活动都处理过
	}
	cout<<ret<<endl;
}
/*
1 4
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 12
2 13
12 14
*/
int main()
{
	int i;
	//输入测试数据
	for(i = 1; i <= N; i++)
	{
		A[i].id = i;
		cin>>A[i].start>>A[i].finish;
	}
	//贪心算法
	Greedy();
	return 0;
}

转自: http://blog.csdn.net/mishifangxiangdefeng/article/details/7747779


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值