ITA 15 动态规划

动态规划和分治法的区别,

首先,分治法:将问题划分成一些独立的子问题,递归的求解各子问题,然后合并子问题的解而得到原问题的解。

动态规划:适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题,鉴于会重复的求解各子问题,DP对每个问题只求解一遍,将其保存在一张表中,从而避免重复计算。

DP算法的设计可以分为四个步骤:

①.描述最优解的结构。
②.递归定义最优解的值。
③.按自底而上的方式计算最优解的值。
④.由计算出的结果创造一个最优解。

例1 装配线问题

#include "stdafx.h"
#include<iostream>
using namespace std;
#include<assert.h>
#include<vector>
#include<queue>
#include<map>
#include<cmath>
#include<string>
#include<stack>

//动态规划,工厂转配线
//e[i]是进入装配线i需要的时间
//x[i]是离开装配线i需要的时间
//a[1][j]表示在装配站S[1][j]所需时间
//t[1][j]表示底盘从S[1][j]移动到S[2][j+1]所需时间,同理t[2][j]
//f[i][j]分别表示在第i装配线上第j个装配站的最优解
//ln[i][j]记录第i条装配线上,最优解时第j个装配站的前一个装配线是什么
// 最优解是,f代表最小花费时间,ln表示最后出来时是从装配线1还是装配线2
const int n = 7;//6站,0不使用
int fastestWay(int l[][n], int f[][n], int a[][n],int t[][n-1],int *e,int *x, int n )
{
	int i = 0, j = 0, ff = 0;
	//初始化
	f[1][1] = e[1] + a[1][1];
	f[2][1] = e[2] + a[2][1];
	
	//站点j依次计算,f[1][j] = min{f[1][j-1]+a[1][j], f[2][j-1]+t[2][j-1]+a[1][j]},
	//同理,f[2][j] = min{f[2][j-1]+a[2][j], f[1][j-1]+t[1][j-1]+a[2][j]}
	for(j = 2; j < n; ++ j)
	{
		if(f[1][j-1]+a[1][j] >= f[2][j-1]+t[2][j-1]+a[1][j])
		{
			f[1][j] = f[2][j-1]+t[2][j-1]+a[1][j];
			l[1][j] = 2;
		}
		else
		{
			f[1][j] = f[1][j-1]+a[1][j];
			l[1][j] = 1;
		}

		if(f[2][j-1]+a[2][j] >= f[1][j-1]+t[1][j-1]+a[2][j])
		{
			f[2][j] = f[1][j-1]+t[1][j-1]+a[2][j];
			l[2][j] = 1;
		}
		else
		{
			f[2][j] = f[2][j-1]+a[2][j];
			l[2][j] = 2;
		}
	}

	if(f[1][n-1]+x[1] < f[2][n-1]+x[2])
	{
		ff = f[1][n-1]+x[1];
		l[1][1] = 1;//利用l[1][1]的空间存放最后经过的装配线
	}
	else
	{
		ff = f[2][n-1]+x[2];
		l[1][1] =2;
	}
	return ff;
}



int main()
{
	int i, j, k;//i装配线个数,j装配线上的站数,k返回值变量,n是站点数+1(1-7)
	int a[3][7],t[3][6];
	int e[3]={0,2,4};
	int x[3]={0,3,2};
	int l[3][7], f[3][7];
	int b[2][6]={{7,9,3,4,8,4}, {8,5,6,4,5,7}};
	int c[2][5]={{2,3,1,3,4},{2,1,2,2,1}};

	int cc[n+1];//用于将l[i][j]中的结果正向输出


	for(i=0; i < 2 ; ++ i)
		for(j=0; j < 6; ++ j)
			a[i+1][j+1] = b[i][j];

	for(i=0; i < 2; ++ i)
		for(j = 0; j < 5; ++ j)
			t[i+1][j+1] = c[i][j];

	k = fastestWay(l,f,a,t,e,x,n);
	cout<<"最少的时间是:"<<k<<endl;
	
	cc[n] = l[1][1];
	for(i=6; i >=2; -- i)
		cc[i] = l[cc[i+1]][i];//l[i][j]记录第i条装配线上,最优解时第j个装配站的前一个装配线是什么
	//cc[i]装配站i的前一站所在的装配线,即i-1所在装配线
	for(i=2; i <=n; ++ i)
		cout<<"汽车装配经过的装配线和装配站情况如下:"<<cc[i]<<","<<i-1<<endl;
}

例2 矩阵乘链

//动态规划,矩阵乘链
//设m[i][j]为计算矩阵Ai...j所需的标量乘法运算次数的最小值;
//对整个问题,计算A1...n的最小代价就是m[1][n]。
//    m[i][j]=0。i=j时
//   m[i][j]=min{ m[i][k]+m[k+1][j]+pi-1pkpj} 在i!=j时。因为Ai是pi-1*pi.
//定义s[i][j]为这样的一个k值:在该处分裂乘积AiAi+1...Aj后可得一个最优加全部括号。
//亦即s[i][j]等于使得m[i][j]取最优解的k值。
const int n = 7;


int matrixChainOrder(int *p, int s[][n] )
{
	int max = 20000;
	int i = 0, j = 0, l = 0, k = 0, q = 0, c = n-1;//7个p,6个矩阵
	int m[n][n];
	//初始化
	for(i = 0; i < n ;++ i)
		m[i][i] = 0;

	for(l=2; l <= c; ++ l) //确定步长,即i与j之间的距离,l=2时表示j-i等于1
	{
		for(i=1; i <= c-l+1; ++ i)//确定起点
		{
			j=i+l-1;//确定终止点
			m[i][j] = max;
			for(k=i; k < j; ++ k)//确定k值,起点与终点间最好的k值
			{
				q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
				if(q < m[i][j])
				{
					m[i][j] = q;
					s[i][j] = k;
				}
			}
		}
	}
	return m[1][n-1];
}
void printOptimalParens(int s[][n],int i,int j)
{
     if(i==j)
         printf("A%d",i);
     else 
     {
         printf("(");
         printOptimalParens(s,i,s[i][j]);
         printOptimalParens(s,s[i][j]+1,j);
         printf(")");
     }
}


int main()
{
	int k;//k返回值,矩阵相乘所需的标量乘法的最小值
	int s[n][n];
	int p[n]={30,35,15,5,10,20,25};
	k=matrixChainOrder(p,s);
	cout<<"矩阵相乘所需的标量乘法的最小值为:"<<k<<endl;
	cout<<"最终的最优全括号形式为:"<<endl;
	printOptimalParens(s,1,6);
}

例 3 最长公共子序列

#include "stdafx.h"
#include<iostream>
using namespace std;
#include<assert.h>
#include<vector>
#include<queue>
#include<map>
#include<cmath>
#include<string>
#include<stack>

//动态规划,最长公共子序列LCS
//给定两个序列x和y,称z是x和y的公共子序列,如果z既是x的子序列,又是y的子序列;
//最长的公共子序列称作最长公共子序列LCS(longest common subsequence)
//(1)LCS的最优子结构
//
//  设zk是xm和yn的一个LCS,则,如果x和y的最后一个元素相同,则z中去掉最后一个元素之后zk-1仍为xm-1和yn-1的LCS
//
//  如果xm!=yn,若zk!=xm,则z是xm-1和y的一个LCS,若zk!=yn,则z是xm和yn-1的LCS。
//
//(2)一个递归解
//
//  设c[i][j]为序列xi和yj的一个LCS的长度,则有:
//
//    c[i][j]=0                                           i=0或j=0
//
//    c[i][j]=c[i-1][j-1]+1                          xi=yj且i,j>0

//        c[i][j]=max(c[i][j-1] , c[i-1][j])           xi!=yj且i,j>0

//(3)计算LCS的长度
void LCS_Length(char* x, char* y, int** c, int** b,int m, int n)
{
	int i = 0, j = 0;
	cout<<m<<","<<n<<endl;
	//初始化
	for(i = 1; i <= m; ++ i)
	{
		c[i][0] = 0;//0列
		//cout<<c[i][0]<<endl;
	}
	for(j = 0; j <=n; ++ j)
	{
		c[0][j] = 0;//0行
		//cout<<c[0][j]<<endl;
	}
	for(i = 1; i <=m; ++ i)
		for(j = 1; j <= n; ++j)
		{
			if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的
			{
				c[i][j] = c[i-1][j-1]+1;
				b[i][j] = 0;
			/*	cout<<c[i][j]<<endl;*/
			}
			else if(c[i-1][j] >= c[i][j-1])//b[i][j]记录最大值来自哪里,左边2,上面1,对角0
			{
				c[i][j] = c[i-1][j];
				b[i][j] = 1;
				//cout<<c[i][j]<<endl;
			}
			else 
			{
				c[i][j] = c[i][j-1];
				b[i][j] = 2;
				//cout<<c[i][j]<<endl;
			}
		}

}

void Print_LCS(int** b, char* x, int i, int j)
{
	if(i == 0 || j == 0)
		return;

	if(b[i][j] == 0)
	{
		Print_LCS(b,x,i-1,j-1);
		cout<<x[i-1];
	}
	else if(b[i][j] == 1)
		Print_LCS(b,x,i-1,j);
	else 
		Print_LCS(b,x,i,j-1);
}


int main()
{
	cout<<"LCS问题,请输入X,Y字符串的长度:"<<endl;
	int m = 0, n = 0, i = 0;
	int** c = new int*[m+1];//c[0...m,0...n]
	int** b = new int*[m+1];//b[1...m,1...n]索引的是从1-m,1-n但是保证可以索引1-m,1-n必须有0-m,0-n的空间
	cin>>m>>n;
	cout<<m<<","<<n<<endl;
	char* x = new char[m];
	char* y = new char[n];
	for(i = 0; i < m; ++ i)
		cin>>x[i];
	//for(i = 0; i < m; ++ i)
	//	cout<<x[i];
	//cout<<endl;
	for(i = 0; i < n; ++ i)
		cin>>y[i];

	//for(i = 0; i < n; ++ i)
	//	cout<<y[i];
	//cout<<endl;
	for(i = 0; i <= m; ++ i)
	{	
		c[i] = new int[n+1];
		b[i] = new int[n+1];
	}

	LCS_Length(x,y,c,b,m,n);

	Print_LCS(b,x,m,n);
}

坑爹的地方1,LCS中第二个循环,(j=0;j<=n;++i)写错了不报错,一直循环赋值,找了半天才找到错误

坑爹的地方2,b[1...m,1...n]记录长度来源的地方,为了使用下标1...m,1...n,需要有0...m,0...n的空间不然访问不存在的空间。

课后习题15.4-2,不使用b的情况,根据c的值判断应该怎么输出,但是还是需要O(M*N)的空间

void Print_LCS(int** c, char* x, int i, int j)
{
	if(i == 0 || j == 0)
		return;

	if(c[i][j] == (c[i-1][j-1]+1))
	{
		Print_LCS(c,x,i-1,j-1);
		cout<<x[i-1];
	}
	else if(c[i][j] == c[i-1][j])
		Print_LCS(c,x,i-1,j);
	else 
		Print_LCS(c,x,i,j-1);
}

15.4-4 如何将空间使用减少到2*min(m,n),首先使用string作为输入,简单方便,其次,细节问题就是因为m,n大小事先不知道,需要判断,使得x中总是保存长的string,这样在循环中保证数组是min(m,n)+O(1)空间,因为例子是7,6数组看不出差别。

两行,一行是pre,一行是cur只保存这两个就够了
void LCS_Length(string x, string y)
{
	int i = 0, j = 0, k=0;
	int m = x.length();
	int n = y.length();
	int t = m > n ? n:m;
	if(m<=n)
	{
		string temp = x;
		x = y;
		y = temp;
		int count = m;
		m = n;
		n = count;
	}

	int *pre = new int[t+1];
	int *cur = new int[t+1];
	//初始化
	for(i = 0; i <= t; ++ i)
	{
		pre[i] = 0;
	}
<span style="white-space:pre">	</span>cur[0] = 0;
	
	for(i = 1; i <=m; ++ i)
	{
		for(j = 1; j <= n; ++j)
		{
			if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的
			{
				cur[j] = pre[j-1]+1; 
			}
			else if(cur[j-1] <= pre[j] )
			{
				
				cur[j] = pre[j];
			}
			else 
			{
				cur[j] = cur[j-1];
			}
		}
		for(k=1; k <= t; ++ k)
		{
			pre[k] = cur[k];
			cur[k] =0;
		}
	}
		cout<<pre[t]<<endl;

}

int main()
{
	cout<<"LCS问题,请输入X,Y字符串:"<<endl;
	string x, y;
	cin>>x;
	cin>>y;
	LCS_Length(x,y);

}

改进2,使得空间使用为min(m,n)+O(1),相当的巧妙,看了好久还画图才明白,



void LCS_Length(string x, string y)
{
	int i = 0, j = 0, k=0;
	int m = x.length();
	int n = y.length();
	int t = m > n ? n:m;
	if(m<=n)
	{
		string temp = x;
		x = y;
		y = temp;
		int count = m;
		m = n;
		n = count;
	}
	int *a = new int[t];
	//初始化
	for(i = 0; i <t; ++ i)
	{
		a[i] = 0;
	}
	int cij = 0,ci1 = 0;
	
	for(i = 1; i <=m; ++ i)
	{
		if(x[i-1] == y[0])
			ci1 = 1;
		else if(a[1]>=0)
			ci1 = a[1];
		a[0] = ci1;
		cout<<ci1;
		for(j = 2; j <= n; ++j)
		{
			if(x[i-1] == y[j-1])//因为x,y都是从0下标开始计算的
			{
				cij = a[j-1]+1; 
				cout<<cij;
			}
			else if(a[0] <= a[j] )
			{
				cij = a[j];
				cout<<cij;
			}
			else 
			{
				cij = a[0];
				cout<<cij;
			}
			a[j-1] = a[0];
			a[0] = cij;
		}
		cout<<endl;

	}
		cout<<a[t-1]<<endl;

}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值