贪心算法基本概念与活动选择问题的求解

1.概念

   贪心算法(Greedy Algorithm):在每一步做出的都是当前看起来最佳的选择。即它总是做出局部最优的选择,寄希望这样的选择能得出全局的最优解。
   贪心算法并不保证一定可以得到最优解,但是对很多问题确实可以得出最优解。

   利用贪心策略设计的算法有:最小生成树、单源点最短路径的Dijkstra、0-1分数背包问题、活动选择问题。

2.贪心算法的原理

     贪心算法通过做出一系列选择来求出问题的最优解,在每个决策点,贪心选择做出的是当前看起来的最佳选择。这种启发式策略并不保证总能找到最优解,但是对一些问题确实有效,比如活动选择问题。

    能用贪心算法的问题应该具有两个性质:1.贪心选择性质,可以通过做出局部最优解来构造全局最优解;2.最优子结构,一个问题的最优解包含子问题的最优解。

3.活动选择问题

    调用竞争共享资源的多个活动的问题,目标是选出一个最大的互相兼容的活动集合。注意:要求的选出“一个最大”,而不是所有最大,说明了贪心算法是得出问题最优解中的一个,而不是全部,并且问题的最优解不一定唯一。另外,“最大”表示的是得出的活动集合包含的活动个数最多。

     每个活动i有一个开始时间和结束时间。实现的原理是:我们可以反复选择最早结束的活动,保留与此活动兼容的活动,重复这一过程,直到不再有剩余活动。

    用递归的方式实现时输入的四个参数 :

 @param s 对应结束时间排好序的活动顺序对应的活动开始时间集合。
 @param f  活动结束的时间,已按照活动结束的时间先后进行排序。
 @param k 指出要求解的子问题Sk(Sk表示在第k个活动结束之后的任务集合)。
 @param n 指出活动的总个数。

     在调用这个方法前,为了使初始化算法更方便,添加一个虚拟活动编号为0(开始和结束时间都是0).这样在求解原问题的时候k初始为0,即为求得的真实的所有活动的解。

   实现的java代码如下:

/**
	 * 本方法采用贪心策略,递归实现:实现的过程中反复选择在剩下的活动中与第k个活动兼容的最早结束的活动,重复这一过程,直到不再有可兼容活动。
	 * 在调用这个方法前,为了使初始化算法更方便,添加一个虚拟活动编号为0(开始和结束时间都是0).这样在求解原问题的时候k初始为0,即为求得
	 * 真实的所有活动的解。
	 * 
	 * @param s 对应结束时间排好序的活动顺序对应的活动开始时间集合。
	 * @param f 活动结束的时间,已按照活动结束的时间先后进行排序。
	 * @param k 指出要求解的子问题Sk(Sk表示在第k个活动结束之后的任务集合)。
	 * @param n 指出活动的总个数。
	 * @return 返回的是Sk对应的一个最大兼容活动集合。
	 */
	public ArrayList<Integer> recurActSelector(int[] s,int[] f,int k,int n){
		ArrayList<Integer> list = new ArrayList<Integer>();
		int m = k+1;
		/*选择出剩下的活动中与第k个活动兼容的最早结束的活动*/
		while(m<=n && s[m]<f[k]){
			m++;
		}
		if(m<=n){
			list.add(m);
			/*继续选择剩下的活动*/
			list.addAll(recurActSelector(s,f,m,n));
			return list;
		}else{
			return list;
		}
	}

看到上面的递归实现过程是一个尾递归的形式,针对尾递归的方式可以转化为迭代实现:

/**
	 * 本方法采用贪心策略,迭代实现:实现的过程中反复选择在剩下的活动中与上一个选中的活动兼容的最早结束的活动,重复这一过程,直到不再有可兼容活动。
	 * @param s 对应结束时间排好序的活动顺序对应的活动开始时间集合。
	 * @param f 活动结束的时间,已按照活动结束的时间先后进行排序。
	 * @return 返回的是选中的一个最大的互相兼容的活动集合。
	 */
	public ArrayList<Integer> iterActSelector(int[] s,int[] f){
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(0);//首先把第一个活动加进去
		int k = 0;//k表示刚选进去的活动编号
		int size = s.length;
		for(int i=1;i<size;i++){
			if(s[i]>=f[k]){//选择下一个与上一次k活动兼容的活动
				list.add(i);
				k=i;
			}
		}
		return list;
	}

特别注意在调用递归实现的时候要在最开始加上一个虚拟的编号为0(开始和结束时间都是0)的活动。

public static void main(String[] args){
		int[] s = {0,1,3,0,5,3,5,6,8,8,2,12};
		int[] f = {0,4,5,6,7,9,9,10,11,12,14,16};
		ActivitySelector test = new ActivitySelector();
		ArrayList<Integer> list1 = test.recurActSelector(s, f, 0, 11);
		System.out.println("list1 = "+list1);
		
		int[] s1 = {1,3,0,5,3,5,6,8,8,2,12};
		int[] f1 = {4,5,6,7,9,9,10,11,12,14,16};
		ArrayList<Integer> list2 = test.iterActSelector(s1, f1);
		System.out.println("list2 = "+list2);
		
	}


你可能感兴趣的:(贪心算法基本概念与活动选择问题的求解)