动态规划初探

最先听说动态规划还是在研究生的最优控制课上,课上介绍了用动态规划解决最优问题。其实动态规划(dynamic programming/DP)和分治方法类似,都是通过组合子问题来求解原问题。不过动态规划应用于问题重叠的情况,即不同的子问题具有公共的子子问题,每个子问题只求一次,而不必重新计算。

就我个人的理解:动态规划就是将所求的问题分步,每一步的最优解是由当前解和之前的最优解组成。求到最后,也就求出了问题的一个最优解,因为可会有多个最优解的情况。

应用DP的步骤:

1.刻画一个最优解的结构特征

2.递归地定义最优值的解

3.计算最优值的解,通常采用自顶向上的方法

4.利用计算出的信息构造一个最优解


一.斐波那契数列问题

运用动态规划解决的问题需要包括最优子结构或重叠子问题。下面就来举一个最常见的用到DP的问题----- Fibonacci数列。

斐波那契数列问题,求F(n)的输出,这是一个最常见的问题,一般是运用递归来做,代码如下:

public static int Fibonacci(int n){
	if(n==1 | n==0)
		return 1;
	return Fibonacci(n-1)+Fibonacci(n-2);
}
这是一个递归函数,但是它的效率不高。假设我们求Fibonacci(4),简写为F(4)。该递归函数的求解过程是这样的

F(4)=F(3)+F(2);

F(3)=F(2)+F(1);

F(2)=F(1)+F(0);

这里的F(2)和F(1)都求了两遍,这里重复求解,使得效率变低。可以使用动态规划,因为这个问题具有重叠子问题。我们可以将之前求出的F(n)放在数组中,而不用再去求它。定义一个数组F(n),用它来存储每个斐波那契的值。代码如下:  用数组存储以求得的子问题,用空间换时间。

public static int Fibonacci(int n){
	int[] F=new int[n+1];
	F[0]=1;
	F[1]=1;
	for(int i=2;i<=n;i++)
	{
		F[i]=F[i-1]+F[i-2];
	}
	return F[n];
}

DP还用来解决如揹包问题,问题描述:给定n种物品和一揹包,物品i的重量是wi,其价值是pi,揹包的容量是M,问如何选择装入揹包中的物品总价值最大?这就是一个最优问题的求解。这里如何求解读者可以自行思考,下面给出更常见到的算法问题。

.最长递增子序列--LIS
问题描述:有一个长为n的数列a0,a1,a2........a(n-1)。请求出这个序列中最长的单增子序列的长度。单增子序列的定义是:对于任意的 i<j,都满足ai<aj。
这里定义一个数组f[i],用来存储数组a[i]为末尾元素的最长子序列的长度。则f[0]=1; f[i]=max(f[j]{j<i,且a[j]<a[i]})+1,(i>1时),如条件不满足,则f[i]=1。返回子序列的长度。代码如下:

 public static int longDPsub(int[] a)
 {
  	if(a.length==0)
   	return 0;
  	int[] f=new int[a.length]; //f[i]代表a[i]处的最长单增子序列的长度
  	f[0]=1;
  	for(int i=1;i<a.length;i++)
  	{
   		f[i]=1;
 	        for(int j=0;j<i;j++)
   		{
    			if(a[j]<a[i] && f[j]+1>f[i])
     			f[i]=f[j]+1;
   		}
   		if(f[i]>length)   //求f[0]到f[i]的最大值
    			length=f[i];
  	}
  	return length;
 }
这里时间复杂度为O(n^2),其实还有更优的算法使时间达到O(n*logn),不过不再是用动态规划解决的。代码如下:
public static int longsub(int[] a)
{       // O(n*logn)算法
	int INF = 1000000;  
	int n =a.length;  
	int[] dp=new int[n]; //dp[i]表示长度为i+1的上升子序列中末尾元素的最小值
	for (int i = 0; i < n; i++)  
	{  
		dp[i] = INF;  
	}  
	for(int i=0;i<n;i++)
	{
		 int l = -1;
		 int r = n;
		 int m;  
		while(l+1!= r){     //内循环:二分查找 
			m = (l+r)/2;  
			if (dp[m] < a[i])  
				l = m;  
			else  
			    r = m;  
		}  
		dp[r]=a[i];
	}
	for(int i=0;i<n;i++)
	{
		if(dp[i]==INF)
			return i;
	}
	return n;
}
上面两种求LIS得算法都只求了长度,而没有求出子序列,如果要输出子序列,就要开辟额外的空间来保存最长子序列的座标信息。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章