当前位置:   article > 正文

算法设计与分析-----动态规划_算法设计与分析动态规划

算法设计与分析动态规划

一、动态规划

1、定义

动态规划是一种解决多阶段决策问题的优化方法,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。

2、动态规划问题的解法

(1)逆序解法

(2)顺序解法

3、动态规划求解的基本步骤

采用动态规划求解的问题的一般要具有3个性质:

  • **最优性原理:**如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优性原理。
  • 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
  • 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。

实际应用中简化的步骤:

① 分析最优解的性质,并刻画其结构特征。

② 递归的定义最优解。

③ 以自底向上或自顶向下的记忆化方式计算出最优值。

④ 根据计算最优值时得到的信息,构造问题的最优解。

4、动态规划与其他方法的比较

​ ①动态规划的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。

在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

​ ②动态规划方法又和贪心法有些相似,在动态规划中,可将一个问题的解决方案视为一系列决策的结果。

不同的是,在贪心法中,每采用一次贪心准则便做出一个不可回溯的决策,还要考察每个最优决策序列中是否包含一个最优子序列。

5、求解整数拆分问题

问题描述】求将正整数n无序拆分成最大数为k(称为n的k拆分)的拆分方案个数,要求所有的拆分方案不重复。

问题求解】设n=5,k=5,对应的拆分方案有:

① 5=5

② 5=4+1

③ 5=3+2

④ 5=3+1+1

⑤ 5=2+2+1

⑥ 5=1+1+1+1

⑦ 5=1+1+1+1+1

为了防止重复计数,让拆分数保持从大到小排序。正整数5的拆分数为7。

采用动态规划求解整数拆分问题。设f(n,k)为n的k拆分的拆分方案个数:

(1)当n=1,k=1时,显然f(n,k)=1。

(2)当n<k时,有f(n,k)=f(n,n)。

(3)当n=k时,其拆分方案有将正整数n无序拆分成最大数为n-1的拆分方案,以及将n拆分成1个n(n=n)的拆分方案,后者仅仅一种,所以有

​ f(n,n)=f(n,n-1)+1。

(4)当n>k时,根据拆分方案中是否包含k,可以分为两种情况:

​ ① 拆分中包含k的情况:即一部分为单个k,另外一部分为{x1,x2,…,xi},后者的和为n-k,后者中可能再次出现k,因此是(n-k)的k拆分,所以这种拆分方案个数为f(n-k,k)。

​ ② 拆分中不包含k的情况:则拆分中所有拆分数都比k小,即n的(k-1)拆分,拆分方案个数为f(n,k-1)。

代码如下:

#include <stdio.h>
#define MAXN 500
int dp[MAXN][MAXN];			//动态规划数组
void Split(int n,int k)		//求解算法
{
	for (int i=1;i<=n;i++)
		for(int j=1;j<=k;j++)
		{
			if (i==1 || j==1)
				dp[i][j]=1;
			else if (i<j)
				dp[i][j]=dp[i][i];
			else if (i==j)
				dp[i][j]=dp[i][j-1]+1;
			else
				dp[i][j]=dp[i][j-1]+dp[i-j][j];
		}
}
int main()
{
	int n=5,k=5;
	Split(n,k);
	printf("(%d,%d)=%d\n",n,k,dp[n][k]);	//输出:7
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

6、求解最大连续子序列和问题

问题描述】给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。

例如

序列(-2,11,-4,13,-5,-2)的最大子序列和为20

序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16

规定一个序列最大连续子序列和至少是0,如果小于0,其结果为0。

问题求解】对于含有n个整数的序列a,设

bj=MAX{ai+ai+1+…+aj} (1≤j≤n)

表示a[1…j]的前j个元素中的最大连续子序列和,则bj-1表示a[1…j-1]的前j-1个元素中的最大连续子序列和。

当bj-1>0时,bj=bj-1+aj,当bj-1≤0时,放弃前面选取的元素,从aj开始重新选起,bj=aj。用一维动态规划数组dp存放b,对应的状态转移方程如下:

dp[0]=0					边界条件
dp[j]=MAX{dp[j-1] +aj,aj} 		1≤j≤n
  • 1
  • 2

则序列a的最大连续子序列和等于dp[j](1≤j≤n)中的最大者。

代码如下:

#include <stdio.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define MAXN 20
//问题表示
int n=6;
int a[]={0,-2,11,-4,13,-5,-2};	//a数组不用下标为0的元素
//求解结果表示
int dp[MAXN];
void maxSubSum()				//求dp数组
{
	dp[0]=0;
	for (int j=1;j<=n;j++)
		dp[j]=max(dp[j-1]+a[j],a[j]);
}
void dispmaxSum()					//输出结果
{
	int k; 
	int maxj=1;
	for (int j=2;j<=n;j++)			//求dp中最大元素dp[maxj]
		if (dp[j]>dp[maxj]) maxj=j;

		
	for (k=maxj;k>=1;k--)		//找前一个值小于等于0者
		if (dp[k]<=0) break;
	printf("    最大连续子序列和: %d\n",dp[maxj]);
	printf("    所选子序列: ");
	for (int i=k+1;i<=maxj;i++)
		printf("%d ",a[i]);
	printf("\n");
}
int main()
{
	maxSubSum();
	printf("求解结果\n");
	dispmaxSum();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

二、动态规划算法实验

1、实验一 fibonacci数列

输入n的值,求得并输出第n个fibonacci数列中的数值

分析

f(1)=1

f(2)=1

f(3)=f(1)+f(2)

f(4)=f(2)+f(3)

如果n<=2
     f(n)=1
否则
     f(n)=f(n-2)+f(n-1)
  • 1
  • 2
  • 3
  • 4

低吗如下:

#include<stdio.h>
int f(int n)
{
	int y;
	if(n<=2)
		y=1;
	else
		y=f(n-2)+f(n-1);
	return y;
} 

int main()
{
	int n,y;
	scanf("%d",&n);
	y=f(n);
	printf("%d\n",y);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

缺点:重复计算,会耗费运行时间。

把每次计算的结果用一个数组来存放!【动态规划解决问题的思路之一】

我们同样会用到动态划分存储空间的操作【动态规划解决问题的思路之一】

改进1:

#include<stdio.h>
//*(memo+i) 等价 memo[i] 
int qiu(int n,int *memo)
{
	if(memo[n]!=-1)
		return memo[n];
	if(n<=2)
		memo[n]=1;
	else
		memo[n]=qiu(n-2,memo)+qiu(n-1,memo);
	printf("n=%d\n",n);
	return memo[n];
}

int f(int n)
{
	int i;
	int memo[n+1];
	for(i=0;i<=n;i++)
		memo[i]=-1;
	return qiu(n,memo);
} 

int main()
{
	int n,y;
	scanf("%d",&n);
	y=f(n);
	printf("%d\n",y);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

动态规划:为了介绍重复计算!【初衷之一】

动态规划:在解决问题的中尽可能的减少内存的耗费!!!【初衷之二】

改进2:

#include<stdio.h>
#include<stdlib.h>
//*(memo+i) 等价 memo[i] 
int f(int n)
{
	int i,a,b;
	if(n<=0)
		return n;
	a=1;
	b=1; 
	for(i=3;i<=n;i=i+2)
	{
		a=a+b;
		b=a+b;
	}
	if(n%2==1)
		return a;
	else
		return b;
} 

int main()
{
	int n,y;
	scanf("%d",&n);
	y=f(6);
	printf("%d\n",y);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2、实验二 拆分方案的个数的求解。

输入一个正整数给n,对n进行拆分(且拆分中的最大数值是k),编程求得当对n进行按最大值为k的拆分方案的个数的求解。

分析:

假设n=5,k=5。答案是7种组合

① 5=5

② 5=4+1

③ 5=3+2

④ 5=3+1+1

⑤ 5=2+2+1

⑥ 5=2+1+1+1

⑦ 5=1+1+1+1+1

为了防止重复计算,让拆分数据的分析一直保持一个状态从大到小的排序!

采用动态规划的思想来求解拆分问题,寻找问题求解的规律【方法】

我们准备一个函数f(n,k),对n进行最大值为k的拆分方案个数的求解

详细分析

第1种情况 当n=1的时候 或者 k=1的时候 ,f(n,k)=1

第2种情况 当n<k的时候 例如f(5,10) 等价 f(5,5)

​ f(n,k)=f(n,n)

第3种情况 当n=k的时候 分成2个部分

​ 【一个包含本身,一个不包含本身】

​ f(n,k)=f(n,n-1)+1

​ 【这里最后+1 其实就是下面黄色一种组合】

① 5=5

② 5=4+1

③ 5=3+2

④ 5=3+1+1

⑤ 5=2+2+1

⑥ 5=2+1+1+1

⑦ 5=1+1+1+1+1

第4种情况 当n>k的时候

(1)拆分中包含k的情况

​ f(n-k,k)

(2)拆分中不包含k的情况

​ f(n,k-1)

最后是f(n,k)=f(n-k,k)+f(n,k-1)

低吗如下:

#include<stdio.h>
int f(int n,int k)
{
	if(n==1 || k==1)
		return 1;
	else if(n<k)
		return f(n,n);
	else if(n==k)
		return f(n,n-1)+1;
	else if(n>k)
		return f(n-k,k)+f(n,k-1);
}

int main()
{
	int n,k,y;
	scanf("%d",&n);
	scanf("%d",&k);
	y=f(n,k);
	printf("%d\n",y);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

改进:

#include<stdio.h>
int a[100][100];
int f(int n,int k)
{
	int i,j;
	for(i=1;i<=n;i++)
		for(j=1;j<=k;j++)
		{
			if(i==1 || j==1)
				a[i][j]=1;
			else if(i<j)
				a[i][j]=a[i][i];
			else if(i==j)
				a[i][j]=a[i][i-1]+1;
			else if(i>j)
				a[i][j]=a[i-j][j]+a[i][j-1];
		}
	return a[n][k];
}

int main()
{
	int n,k,y;
	scanf("%d",&n);
	scanf("%d",&k);
	y=f(n,k);
	printf("%d\n",y);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/87195
推荐阅读
相关标签
  

闽ICP备14008679号