【算法】动态规划(一):求最大不重复序列的最大权值

最近刷题看到了好多的动态规划的题目,很多题目看上去都一知半解。可能是因为自己对动态规划好不够了解吧,所以来做点笔记。

今天这个题目是在网上看到的,不知道起什么名字就随便起了这样一个名字。

问题的大意是:在一定的时间内有若干个任务,每个任务有起止时间和自身的价值(按照讲者的意思是干完了给多少钱),现在要给出在规定时间内不重叠但价值最大的任务序列(因为一个人不能同时干两件事情)

【算法】动态规划(一):求最大不重复序列的最大权值_第1张图片

我们知道算法导论上说动态规划应用于重叠子问题和具有最优子结构的时候,对每子问题都只求解一次避免了重复计算。主要的思路是用空间换时间,将每个子问题的结果保存起来供之后用到的时候使用,避免再次计算子问题。

对于每个任务,我们可以有两种选择:选或者不选。例如,对于任务8,如果我们选择了它,那么做完我们可以得到四个价值,消耗的是从时间8到时间11的时间,并且,如果我们选择了任务8,那么任务6和7就不能选了,因为他们同任务8有相同的时间段。所以如果选择任务8的话,下一次可选的任务序列号最大的是任务5。

对于每个任务,如果我们选择了它,那么它之前的任务有的就无法选择了,我们将每个任务i选择后,可选的序列号最大的任务记做prev[i].

对于本问题,prev[i] 为下表:

item 0 1 2 3 4 5 6 7 8
prev 0 0 0 0 1 0 2 3 5

我们设置一个任务0 ,它的起止时间和价值都是0,当任务前没有可选时间时,可选任务0.

那么,对于本题目,从序列最高的任务开始自顶向下,每一个任务都有选或者不选两种选择,我们当前所有任务的总价值为OPT[i],那么其树形结构如下图所示:

【算法】动态规划(一):求最大不重复序列的最大权值_第2张图片

如图所示:如果选择的任务8,那么当前所有任务总价值等于任务8的价值加上选了任务8后可选的序列最大的任务的总价值。本题中是任务5,;如果不选择任务8,那么所有任务总价值等于前7个任务的总价值OPT[7].

如此继续下去,我们发现右边的树需要计算OPT[3],左边也要计算OPT[3].为了不进行重复的计算,我们将OPT[3]在计算后保存下来,这样将就会节省很多计算的时间。

对于有n个任务的问题,我们要计算OPT[n]时,OPT[0]-OPT[n-1]都被计算过了并保存下来了。这样计算OPT[n]就不需要将之前的任务选择与否都重复计算了。

当然OPT[0] = 0. 因为我们加进了任务0(不加时也是0,这样只是便于理解),其选择与不选择,价值都是0。

计算的公式如下:

OPT(i) =\left\{\begin{matrix}Max(OPT(prev[i])+value[i], OPT(i-1)) & i>0 \\ 0 & i=0 \end{matrix}\right.

那么,本问题中OPT的价值为下表:

item 0 1 2 3 4 5 6 7 8
OPT 0 5 5 8 9 9 9 10 13

 

同时,有兴趣的可以保存每个OPT时所选择的任务序列。

下面是实现JAVA代码:由于我加入了任务序列的保存和prev的计算。所以总体时间复杂度应该是O(N2), 正统的求最大价值的时间复杂度是O(N),空间复杂度也是O(N)

public class MaxNumsSolution {
	List> list;
	int[] opt,prev,value;
	
	MaxNumsSolution(int[] starttime,int[] endtime, int[] value2){
		list = new ArrayList>();
		
		List zero = new ArrayList();
		list.add(zero);
		
		opt = new int[value2.length+1];
		prev = MakePrev(starttime,endtime);
		value = value2;
	}
	
	//计算prev
	public int[] MakePrev(int[] starttime,int[] endtime) {
		int[] prev = new int[starttime.length];
		for(int i  = starttime.length-1;i>=0;i--) {
			for(int j = i; j >= 0;j--) {
				if(endtime[j] <= starttime[i]) {
					prev[i] = j;
					break;
				}
			}
		}
		return prev;
	}
	
	//显示结果
	public void show() {
		System.out.println("所有任务的不重合最大权值:"+opt[value.length-1]);
		System.out.println("最大权值选择的任务:"+ list.get(list.size()-1).toString());
	}
	
	//计算每个的最大值
	public void fun() {
		opt = new int[value.length];
		opt[0] = 0;
		for(int i = 1; i  selected = new ArrayList();;
			if(value[i]+opt[prev[i]]>=opt[i-1]) {
				opt[i] = value[i]+opt[prev[i]];
				selected.addAll(list.get(prev[i]));
				selected.add(i);
				list.add(selected);
			}
			else {
				opt[i] = opt[i-1];
				selected.addAll(list.get(i-1));
				list.add(selected);
			}
		}
		show();
	}
	
	
	public static void main(String[] args) {
        //(0,0,0)(1,3,5)(3,5,2)(0,6,8)(4,7,4)(3,8,6)(5,9,3)(6,10,2)(8,11,4)
		int[] start = {0,1,3,0,4,3,5,6,8};
		int[] end = {0,4,5,6,7,8,9,10,11};
		int[] value = {0,5,2,8,4,6,3,2,4};
		
		new MaxNumsSolution(start,end,value).fun();;
	}

 

你可能感兴趣的:(数据结构)