首页 > 代码库 > 动态规划

动态规划

动态规划(DP)是一种解决复杂问题特别是主问题包括反复子问题的经常使用算法思想。它将待求解问题分解成若干子问题,先求解子问题,然后再从子问题中得到原问题的解。

不同于分治法,动态规划的子问题经常不是互相独立的,下一阶段的求解建立在上一阶段的解的基础上进行的。

利用动态规划求解问题的有效性 依赖于问题本身具有的两个重要性质。也就是假设待解决这个问题具有下面两个性质,就能够考虑使用动态规划求解。

1 最优子结构:问题的最优解包括了其子问题的最优解。

2 重叠子问题:在求解过程中,如使用递归自顶向下求解问题时,每次产生子问题并不总是新的,有的被反复计算多次。故须要将子问题的解保存下来,供以后直接使用。

这样的思   想又叫“记表备查”,将求解过程中的最优值保存在决策表中。

求斐波那契数列问题

1 2 3 5 8 13.....    f(n) = f(n - 1) + f(n - 2)   f(1) = 1, f(2) = 2

   使用递归例如以下:

int Fbnaci(int n) {
    if(n == 1)
        return 1;
    if(n == 2)
        return 2;
    if(n > 2)
        return Fbnaci(n - 1) + Fbnaci(n - 2);
}
上述代码尽管简洁,可是相当费时,由于有着大量反复问题的计算。如计算f(10)须要计算f(9) 和 f(8) 计算f(9)时又须要计算f(8) .使用动态规划的实现例如以下:
int Fbnaci(int n) {
    int i,*a;
    if(n==1)
        return 1;
    if(n==2)
        return 2;
    if(n>2)
    {
        a=(int *)malloc(sizeof(int)*n);
        a[0]=1;
        a[1]=2;
        for(i=2;i<n;i++)
            a[i]=a[i-1]+a[i-2];
        return a[n-1];    
    }
}
以上代码尽管比前面递归的代码量大。可是使用数组保存了中间结果。以后调用时不必再计算。直接使用数组值就能够,从而大大降低了运算时间。(空间换时间)


求(LCS)最大公共子序列问题

给定两个字符串。求它们共同的最长子序列,(不一定连续)。

对于字符串 X = {x1,x2,...,xm}, Y = {y1,y2,...,yn } 求 Z = {z1,z2,....zk }  当中 zi 同一时候在字符串X和Y中,且顺序保持不变。

如 X = “ABCBDAB”  Y="BDCABA "  最长公共子序列为  BCBA 和 BDAB

最优子结构性质:

1 若 x(m) = y(n) 则 z(k) =  x(m) = y(n) 且 {Zk-1} 是 {Xm-1} 和 {Yn-1}的最长公共子序列。

2  若 x(m) != y(n) 且 z(k) != x(m) 则  {Zk} 是{Xm-1}和{Yn} 的最长公共子序列

3 若 x(m) != y(n) 且 z(k) != y(n) 则  {Zk} 是{Yn-1}和{Xm} 的最长公共子序列

简单来说。假设X和Y的最后一个元素同样,则最后一个元素肯定在Z中且是最长子序列的最后一个元素。那么Z的长度为{Xm-1} 和 {Yn-1}的最长公共子序列的长度 + 1

否则最长公共子序列的长度为 : max{ length(Xm-1,Yn) ,  length(Xm,Yn-1)}

用 数组 c[ i ][  j ]记录X[ 0 ~ i ] 与 Y[ 0 ~ j ] 的最长公共子序列的长度 则:


c[i][j] =  0                                              if  i = 0, j = 0;

 =  c[i-1][j-1] + 1                     else if  xi = yj

 =  max{ c[i][j-1] ,c[i-1][j] }          else if  xi != yj


以  X = “ABCBDAB”  Y="BDCABA " 为例,求得矩阵C例如以下:(參考算法导论)

技术分享

当中箭头方向表示当前子问题的解(箭尾) 由上一个子问题(箭头)的最优解得来。

如 c[2][1] = c[1][0] +1 得来;表示i= 2 ,j = 1 时 x2 = y1 = ‘B’ ,则当前子问题最长子序列长度 = 上一子问题最长子序列长度  + 1;其余类似。

详细代码例如以下:

// Length of LCS
int LCSLength(int m,int n,const char x[],char y[])
{
	int i,j;
	for(i = 1; i <= m; i++)//初始第一行
		c[i][0] = 0;
	for(j = 1; j <= n; j++)//初始第一列
		c[0][j] = 0;
	for(i = 1; i <= m; i++)
	{
		for(j = 1; j <= n; j++)
		{
			if(x[i-1] == y[j-1])  //相等
			{
				c[i][j] = c[i-1][j-1] + 1;
				b[i][j] = 1;
			}
			else if(c[i-1][j] >= c[i][j-1])
			{
				c[i][j] = c[i-1][j];
				b[i][j] = 2;
			}
			else
			{
				c[i][j] = c[i][j-1];
				b[i][j] = 3;
			}
		}
	}
	return c[m][n];
} 
//依据数组b内容打印x和y的最大公共子序列
void LCSprint(int i,int j,char x[])
{
	if(i == 0 || j == 0)
		return;
	if(b[i][j] == 1)
	{
	//	printf("%c",x[i-1]);
		LCSprint(i-1,j-1,x);
		printf("%c",x[i]);
		
	}
	else if(b[i][j] == 2)
		LCSprint(i-1,j,x);
	else
		LCSprint(i,j-1,x);
}

注意以上代码中字符串下标从1 開始。

数塔问题:


给定数字三角形,计算从顶至底的某一条路径使该路径所经过的数字的总和最大。

技术分享

        

使用二维数组存储上述数据

7

3 8

8 1 0

2 7 4 4

逆向思考,从倒数第二层算起,分别计算每一层全部元素可能达到的最大值,从下往上进行,到第一行时就得到路径最大值。

状态转移方程:C[ i ] [ j ]   + =  max{ C[ i+1] [ j ] 。 C[ i+1] [ j +1]  }


   






动态规划