`
Touch_2011
  • 浏览: 290648 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论
阅读更多
1、             基本思想

将一个问题分解为子问题递归求解,且将中间结果保存以避免重复计算。通常用来求最优解,且最优解的局部也是最优的。求解过程产生多个决策序列,下一步总是依赖上一步的结果,自底向上的求解。

2、             一般步骤

A.            寻找子问题,对问题进行划分。一般是分几种情况,有时是模拟现实中的情况

B.             定义状态。往往将和子问题相关的各个变量的一组取值定义为一个状态。某个状态的值就是这个子问题的解。

C.             找出状态转移方程。一般是从一个状态到另一个状态时变量值改变。

D.            若有K个变量,一般用K维的数组存储各个状态下的解,并可根据这个数组记录打印求解过程。

E.              动态规划并没有具体的模式,应具体情况具体分析。

3、             例题分析

1)求从三角形顶部到底部和最大的路径,即最佳路径

    1

   3 4

  5 7 4

 5 6 7 8

   分析:这题一看,可以用回溯法。子问题:从(ij)开始,到底部的最佳路径。状态就是(ij)坐标。比如数字1,要么从左边走,要么从右边走。所以状态转移方程:

maxSum(i,j)=max{ maxSum(i+1,j) , maxSum(i+1,j+1)}  (i<3)

maxSum(i,j)=a[i][j]  (i==3) 假设有从0行开始,且用a数组存储三角形。

 代码:

//求(ij)坐标到底部的最佳路径

int maxSum(int i,int j)

{

       int sumLeft,sumRight;

       if(i==N-1){

              return triangle[i][j];

       }

       if(remarkSum[i+1][j]==-1)//没计算过,则计算

              remarkSum[i+1][j]=maxSum(i+1,j);

    if(remarkSum[i+1][j+1]==-1)

              remarkSum[i+1][j+1]=maxSum(i+1,j+1);

       sumLeft=remarkSum[i+1][j];

       sumRight=remarkSum[i+1][j+1];

       return sumLeft>sumRight?sumLeft+triangle[i][j]:sumRight+triangle[i][j];

}

//打印路径

void print(int i,int j)

{

       if(i>=N)

              return;

       printf("%-4d",triangle[i][j]);

       if(remarkSum[i+1][j]>remarkSum[i+1][j+1])

              print(i+1,j);//左边的和比右边的大,则打印左边的数

       else

              print(i+1,j+1);//右边的和比左边的大,则打印右边的数

}

2)最长上升子序列,如aghbcz的最长上升子序列长度是4abcz

分析:子问题是以str[i]为终点的最长上升子序列。状态是str[i].要求出str[i]状态中对应的各个子问题的解的最大值。状态转移方程:要求某一个状态的最长上升子序列,先要求出在这个终点的左边各个点的最长上升子序列,选择其中的最大值且字符的值小于当前要求的点上的字符值。up(i)=max{ up(k) && str[i]>str[k]} 其中k0i.

代码:

int up(int n)

{

       int i,temp=0;

       if(n==0){

              return remark[0]=1;

       }

       //选择a[0]a[n]中比n小且最长子序列最大的那个值

       for(i=n;i>=0;i--){

              if(a[i]<a[n]){

                     if(remark[i]==0)

                     remark[i]=up(i);          

               if(remark[i]>temp)

                            temp=remark[i];

              }

       }

       return remark[n]=1+temp;

}

3)最长公共子序列

分析:顺次比较两个序列的各个值,有三种情况。

递归方程:

i = 0 , j = 0 , c[i][j] = 0
i , j > 0 ; xi = yi , c[i][j] = c[i-1][j-1] + 1
i , j > 0 ; xi != yi , c[i][j] = max { c[i][j-1] , c[i-1][j] }

代码:

//返回最长公共子序列的长度

int sub(int i,int j)

{

       if(str1[i]=='\0' || str2[j]=='\0')

              return 0;

       if(str1[i] == str2[j]){

              if(remark[i+1][j+1]<0)

              remark[i+1][j+1]=sub(i+1,j+1);

              return remark[i+1][j+1]+1;

       }

       else{

              if(remark[i][j+1]<0)

           remark[i][j+1]=sub(i,j+1);

              if(remark[i+1][j]<0)

           remark[i+1][j]=sub(i+1,j);

              return remark[i+1][j] > remark[i][j+1] ?

                     remark[i+1][j] : remark[i][j+1];

       }

}

4)矩阵连乘

分析:A[i]* ...*A[j],以A[k]为分界点,分为A[i]*...*A[k]A[k+1]*...*A[j]两部分。这两个字部分也要是最优解。用remark[i][j]记录各个状态的值。

代码:

//AiAj连乘乘法计算的最少次数

int matrixMul(int i,int j)

{

       int k,min=0;

    if(i==j){

              return 0;

       }

       //A[k]为分界点,分为A[i]*...*A[k]A[k+1]*...*A[j]两部分

    for(k=i;k<j;k++){

              if(remark[i][j]==0)

               remark[i][j]=matrixMul(i,k)+matrixMul(k+1,j)

                      +A[j].column*A[i].row*A[k].column;

              if(min==0){

                     min=remark[i][j];

              }

              else if(remark[i][j]<min){

                     min=remark[i][j];

              }

       }

       return min;

}

5)旅行商问题:从一个城市出发,遍历所有城市(每个城市只走一次),最后回到原点。求花费的最小代价。

分析:min{下一个结点到始点的最短路径+这个点到下一个结点的路径}

代码:

//从顶点i开始遍历canUseVertex里的城市最终到达0(开始点)的最短路径

int shortestPath(int i,int* canUseVertex,int length)

{

       int temp,tempMin;

       int min=INFINITE,j,k;

       if(length==0)

              return graph[i][0];

       for(j=0;j<length;j++){

           temp=canUseVertex[j];

              for(k=j;k<length-1;k++){

                     canUseVertex[k]=canUseVertex[k+1];

              }

 

              tempMin=shortestPath(temp,canUseVertex,length-1)

                     +graph[i][canUseVertex[j]];

 

              for(k=length-1;k>j;k--)

                     canUseVertex[k]=canUseVertex[k-1];

              canUseVertex[j]=temp;

 

              if(tempMin<min)

                     min=tempMin;

       }

       return min;

}

6)图中两点的最短路径。

     分析:min{下一个结点到终点的最短路径+这个结点到下一个结点的路径}

70/1背包问题

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]

4、             总结

动态规划和贪心算法相同与不同:

相同点:

动态规划和贪心算法都是一种递推算法;

均有局部最优解来推导全局最优解

 

不同点:

贪心算法:

 1.贪心算法中,作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。

 2.由(1)中的介绍,可以知道贪心法正确的条件是:每一步的最优解一定包含上一步的最优解。

动态规划算法:

1.全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解。(如背包问题)

2.动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优。

3.边界条件:即最简单的,可以直接得出的局部最优解。

 

 

 

 

 

 

 

/*
 * 矩阵连乘计算次数最少
 */

#include<stdio.h>

typedef struct 
{
	int row; //矩阵行数
	int column;//矩阵列数
}Matrix;

Matrix A[20];//存储要相乘的矩阵
int remark[20][20]={0};//存储Ai到Aj连乘乘法计算的最少次数


//Ai到Aj连乘乘法计算的最少次数
int matrixMul(int i,int j)
{
	int k,min=0;
    if(i==j){
		return 0;
	}
	//以A[k]为分界点,分为A[i]*...*A[k]和A[k+1]*...*A[j]两部分
    for(k=i;k<j;k++){
		if(remark[i][j]==0)
    		remark[i][j]=matrixMul(i,k)+matrixMul(k+1,j)
		    	+A[j].column*A[i].row*A[k].column;
		if(min==0){
			min=remark[i][j];
		}
		else if(remark[i][j]<min){
			min=remark[i][j];
		}
	}
	return min;
}

void print(int i,int j)
{
	int k,temp;
	for(k=i;k<j;k++){
		temp=matrixMul(i,k)+matrixMul(k+1,j)
		    	+A[j].column*A[i].row*A[k].column;
		if(temp==remark[i][j])
			break;
	}
	if(k<j && i<k<j){
	//	printf("A[%d]*...*A[%d]\n",i,k);
	//	printf("A[%d]*...*A[%d]\n",k,j);
		printf("A[%d]  ",k);
		print(i,k);
		print(k+1,j);
	}
}

void main()
{
	int i,n;
	printf("请输入要相乘的矩阵个数,然后输入每个矩阵的行数和列数\n");
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d%d",&A[i].row,&A[i].column);
	printf("\n这些矩阵相乘乘法计算的最少次数是:%d\n",matrixMul(0,n-1));
	print(0,n-1);
	printf("\n");
}

 

 

/*求从三角形顶部到底部和最大的路径,即最佳路径(动态规划)
 *   1
 *  3 4
 * 5 7 4
 *5 6 7 8
 */

#include<stdio.h>
#define N 4

int triangle[N][N]={//三角形
	{1},
	{3,4},
	{5,7,4},
	{5,6,7,8}
};

int remarkSum[N][N];//记录maxSum(i,j)的值,避免重复计算

//求(i,j)坐标到底部的最佳路径
int maxSum(int i,int j)
{
	int sumLeft,sumRight;
	if(i==N-1){
		return triangle[i][j];
	}
	if(remarkSum[i+1][j]==-1)//没计算过,则计算
		remarkSum[i+1][j]=maxSum(i+1,j);
    if(remarkSum[i+1][j+1]==-1)
		remarkSum[i+1][j+1]=maxSum(i+1,j+1);
	sumLeft=remarkSum[i+1][j];
	sumRight=remarkSum[i+1][j+1];
	return sumLeft>sumRight?sumLeft+triangle[i][j]:sumRight+triangle[i][j];
}

//打印路径
void print(int i,int j)
{
	if(i>=N)
		return;
	printf("%-4d",triangle[i][j]);
	if(remarkSum[i+1][j]>remarkSum[i+1][j+1])
		print(i+1,j);//左边的和比右边的大,则打印左边的数
	else
		print(i+1,j+1);//右边的和比左边的大,则打印右边的数
}

void main()
{
	int i;
	for(i=0;i<N*N;i++)
		*(*remarkSum+i)=-1;
	printf("%d\n",maxSum(0,0));
	print(0,0);
	printf("\n");
}

 

 

/*
 * 最长公共子序列
 */

#include<stdio.h>
#include<string.h>

#define M 20
#define N 20

int remark[M][N];

char *str1="programming";
char *str2="contest";

//返回最长公共子序列的长度
int sub(int i,int j)
{
	if(str1[i]=='\0' || str2[j]=='\0')
		return 0;
	if(str1[i] == str2[j]){
		if(remark[i+1][j+1]<0)
              remark[i+1][j+1]=sub(i+1,j+1);
		return remark[i+1][j+1]+1;
	}
	else{
		if(remark[i][j+1]<0)
           remark[i][j+1]=sub(i,j+1);
		if(remark[i+1][j]<0)
           remark[i+1][j]=sub(i+1,j);
		return remark[i+1][j] > remark[i][j+1] ? 
			remark[i+1][j] : remark[i][j+1];
	}
}

//打印
void print(int i,int j)
{
	if(str1[i]=='\0' || str2[j]=='\0')
		return;
	if(str1[i]==str2[j]){
		putchar(str1[i]);
		print(i+1,j+1);
	}
	else if(remark[i][j+1]>remark[i+1][j])
		print(i,j+1);
	else
    	print(i+1,j);
}

void main()
{
	int i,j,maxLength=0;
	for(i=0;i<N*M;i++)
		*(*remark+i)=-1;
	for(i=0;i<strlen(str1);i++)
		for(j=0;j<strlen(str2);j++)
			if(maxLength<sub(i,j))
				maxLength=sub(i,j);
	printf("%d\n",maxLength);
	print(0,0);
	printf("\n");
}

 

 

/*
 * 最长上升子序列
 */

#include<stdio.h>

int a[50];//输入的序列
int len;//长度
int remark[50]={0};//记录a中各个数的最长上升子序列

//求序列中下标为n的上升子序列
int up(int n)
{
	int i,temp=0;
	if(n==0){
		return remark[0]=1;
	}
	//选择a[0]到a[n]中比n小且最长子序列最大的那个值
	for(i=n;i>=0;i--){
		if(a[i]<a[n]){
			if(remark[i]==0)
		     	remark[i]=up(i);		
	        if(remark[i]>temp)
				temp=remark[i];
		}
	}
	return remark[n]=1+temp;
}

void main()
{
	int i,indexMax=0,j,index;
	printf("input length:\n");
	scanf("%d",&len);

    for(i=0;i<len;i++)
		scanf("%d",&a[i]);

	//求每个值的最长升序子序列
	for(i=0;i<len;i++)
    	if(up(i)>remark[indexMax])
			indexMax=i;
	printf("%d\n",remark[indexMax]);
	index=indexMax;

	//打印
	for(i=0;i<remark[indexMax];i++){
	    printf("%-4d",a[index]);
		for(j=index;j>=0;j--)
			if(remark[j]==remark[index]-1 && a[j]<a[index]){
				index=j;
				break;
			}
	}
	printf("\n");
}

 

 

/*
 * 旅行商问题
 */

#include<stdio.h>
#define INFINITE 1000

int m,n;

int graph[5][5]={//邻接矩阵存储图
	{INFINITE,3,INFINITE,4,3},
	{3,INFINITE,1,5,8},
	{INFINITE,1,INFINITE,6,INFINITE},
	{4,5,6,INFINITE,2},
	{3,8,INFINITE,2,INFINITE}
};
//未走过的城市,既未遍历的图的顶点,默认出发点已经遍历过
int canUseVertex[5]={1,2,3,4};
int length=4;//没遍历的城市个数

int remark[5][5]={0};//存储中间状态的最优解

//从顶点i开始遍历canUseVertex里的城市最终到达0(开始点)的最短路径
int shortestPath(int i,int* canUseVertex,int length)
{
	int temp,tempMin;
	int min=INFINITE,j,k;
	if(length==0)
		return graph[i][0];
	for(j=0;j<length;j++){
  		temp=canUseVertex[j];
		for(k=j;k<length-1;k++){
			canUseVertex[k]=canUseVertex[k+1];
		}

		tempMin=shortestPath(temp,canUseVertex,length-1)
			+graph[i][canUseVertex[j]];

		for(k=length-1;k>j;k--)
			canUseVertex[k]=canUseVertex[k-1];
		canUseVertex[j]=temp;

		if(tempMin<min)
			min=tempMin;
	}
	return min;
}

void main()
{
	printf("%d\n",shortestPath(0,canUseVertex,4));

}

 

分享到:
评论

相关推荐

    从《鹰蛋》一题浅析对动态规划算法的优化.ppt

    从《鹰蛋》一题浅析对动态规划算法的优化.ppt

    浅析对动态规划算法的优化.ppt

    浅析对动态规划算法的优化.ppt

    浅析生物信息学动态规划算法.pdf

    【生物信息学动态规划算法详解】 生物信息学是利用计算机科学和统计技术处理生物学数据的领域,其中动态规划算法在解决复杂序列分析问题上扮演着关键角色。动态规划是一种优化策略,通过分解问题并逐阶段求解,找到...

    算法合集之《从《鹰蛋》一题浅析对动态规划算法的优化》1

    总的来说,《从《鹰蛋》一题浅析对动态规划算法的优化》这篇文章为我们提供了一个生动的实例,展示了如何通过多种方法优化动态规划算法,以及优化背后的思想。这对于我们学习和应用动态规划有着重要的指导意义,提醒...

    算法合集之从鹰蛋一题浅析对动态规划算法的优化PPT学习教案.pptx

    动态规划是一种在计算机科学中广泛使用的算法,尤其在解决最优化问题时表现出高效性。它通过将大问题分解成小问题的子集,利用子问题的解构建原...通过学习,可以提升对动态规划算法的理解,提高解决实际问题的能力。

    浅析C语言递归算法

    - **动态规划**:在动态规划问题中,递归可以帮助找到问题的最优解。 - **分治算法**:例如快速排序、归并排序等算法都采用了递归的思想来解决问题。 #### 六、递归技巧与注意事项 1. **避免无限递归**:确保递归...

    浅析模拟算法

    在工程领域,模拟算法常用于测试和优化设计,例如在交通规划中模拟车流,或在电力系统中模拟电力分配。在社会科学中,模拟可以帮助我们理解复杂的社会动态,如人口迁移、经济政策的影响等。 实现模拟算法时,关键...

    浅析线性规划方法在半导体制造计划中的应用.pdf

    "浅析线性规划方法在半导体制造计划中的应用" 本文讨论了线性规划方法在半导体制造计划中的应用。半导体制造业中产品种类繁多,过程复杂,对设备的利用率要求较高,因而相对于其他制造业来说,生产计划的优化也较为...

    图的遍历迷宫算法浅析

    例如,我们可以使用动态规划来优化迷宫的生成过程。我们可以使用贪心算法来选择遍历的方向,并使用回溯算法来避免重复遍历。 图的遍历迷宫算法可以生成各种复杂的迷宫,从简单的迷宫到复杂的迷宫。这种算法可以应用...

    浅析回溯算法

    同时,为了提高效率,可以利用动态规划、记忆化搜索等技术,存储之前计算过的中间结果,避免重复计算。 总的来说,回溯算法是一种强大的问题求解策略,适用于解决具有大量可能性的问题。理解其基本原理并学会灵活...

    浅析0/1背包问题.pdf

    - **动态规划算法**:使用一个二维数组dp[i][j]来记录前i个物品放入容量为j的背包中所能获得的最大价值。通过递推关系式dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]),从底向上计算所有子问题的解。 #### 3. ...

    HMM隐马尔科夫模型三大问题与算法浅析易懂

    算法通过动态规划逐步找到具有最高概率的路径。 - **EM算法(Expectation-Maximization Algorithm)**:在HMM中,EM算法用于在没有完整标签数据的情况下估计模型参数。E步骤(期望步骤)计算条件期望,M步骤(最大...

    浅析java贪心算法

    贪心算法是一种优化策略,它在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果...然而,对于某些问题,如旅行商问题,贪心算法可能无法找到最优解,此时需要使用其他算法,如动态规划。

    算法文档无代码浅析倍增思想

    动态规划(Dynamic Programming,DP)是一种优化算法,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。DP的核心是通过子问题的解来递推原问题的解,而倍增思想在DP中常用于减少重复计算,优化时间...

    算法文档无代码浅析用极大化思想解决最大子矩形问题

    解决最大子矩形问题的一个经典算法是基于动态规划的方法。动态规划是一种将复杂问题分解为更小的子问题,并存储这些子问题的解(通常是在一个表格中),以避免重复计算的方法。动态规划的关键在于识别问题的子结构和...

    算法合集之《浅析竞赛中一类数学期望问题的解决方法》

    ### 算法合集之《浅析竞赛中一类数学期望问题的解决方法》 #### 引言 在概率论和统计学中,数学期望是一个非常重要的概念,它被广泛应用于生活中的各种场景,比如彩票设计、投资决策等。而在信息学竞赛中,求解...

    浅析遗传算法在电力系统中的应用.pdf

    随着电力系统规模的不断扩大和复杂度的提高,传统求解方法暴露出越来越多的问题,遗传算法以其独有的特点和优势在电力系统相关问题的求解中异军突起,在电网规划、无功优化、故障恢复等问题中得到了广泛应用。...

Global site tag (gtag.js) - Google Analytics