第15章 动态规划

一、综述

动态规划是通过组合子问题的解而解决整个问题的。
动态规划适用于子问题不是独立的情况,也就是各子问题的包含公共的子子问题。
动态规划对每个子问题只求解一次,将其结果保存在一张表中。
动态规划通常用于最优化问题。
动态规划的设计步骤:a.描述最优解的结构b.递归定义最优解的值c.按自底向上的方式计算最优觖的值d.由计算出的结构构造一个最优解

二、代码

15.1装配线调度

#include <iostream>
using namespace std;

#define I 2
#define J 6
int a[I+1][J+1], t[I+1][J+1], e[I+1], x[I+1], n = J;//输入
int f[I+1][J+1], l[I+1][J+1],rf,rl;                 //输出
//书上的伪代码
void Fastest_Way()
{
	//初始化
	f[1][1] = e[1] + a[1][1];
	f[2][1] = e[2] + a[2][1];
	int j;
	for(j = 2; j <= n; j++)
	{
		//根据公式15.6计算
		if(f[1][j-1] + a[1][j] <= f[2][j-1] + t[2][j-1] + a[1][j])
		{
			//记录计算结果
			f[1][j] = f[1][j-1] + a[1][j];		
			l[1][j] = 1;
		}
		else
		{
			//记录计算结果
			f[1][j] = f[2][j-1] + t[2][j-1] + a[1][j];
			l[1][j] = 2;
		}
		//根据公式15.7计算
		if(f[2][j-1] + a[2][j] <= f[1][j-1] + t[1][j-1] + a[2][j])
		{
			//记录计算结果
			f[2][j] = f[2][j-1] + a[2][j];
			l[2][j] = 2;
		}
		else
		{
			//记录计算结果
			f[2][j] = f[1][j-1] + t[1][j-1] + a[2][j];
			l[2][j] = 1;
		}
	}
	//最后一个结果另外记录
	if(f[1][n] + x[1] <= f[2][n] + x[2])
	{
		rf = f[1][n] + x[1];
		rl = 1;
	}
	else
	{
		rf = f[2][n] + x[2];
		rl = 2;
	}
}
//输出
void Print_Stations()
{
	cout<<"输出装配路线"<<endl;
	int i = rl, j;
	//从后向前输出
	cout<<"line "<<i<<" station "<<n<<endl;
	for(j = n; j > 1; j--)
	{
		//获取记录的结果
		i = l[i][j];
		cout<<"line "<<i<<" station "<<j-1<<endl;
	}
}
//练习
//15.1-1
void Print_Stations2(int i, int j)
{
	cout<<"顺序输出装配路线"<<endl;
	if(j != n)
		i = l[i][j+1];
	//先输出前面的
	if(j > 1)
		Print_Stations2(i, j-1);
	//再输出当前的
	cout<<"line "<<i<<" station "<<j<<endl;
}
//输入数据
void Input()
{
	int i, j;
	cout<<"输入在装配站S(i,j)的装配时间a(i,j):"<<endl;
	//7 9 3 4 8 4 8 5 6 4 5 7
	for(i = 1; i <= I; i++)
	{
		for(j = 1; j <= J; j++)
			cin>>a[i][j];
	}
	cout<<"输入进入装配线i的花费时间e(i):"<<endl;
	//2 4
	for(i = 1; i <= I; i++)
		cin>>e[i];
	cout<<"输入从S(i,j)站移动S(i2,j+1)的花费时间t(i,j):"<<endl;
	//2 3 1 3 4 2 1 2 2 1
	for(i = 1; i <= I; i++)
	{
		for(j = 1; j < J; j++)
			cin>>t[i][j];
	}
	cout<<"输入从装配线i离开的花费时间x(i):"<<endl;
	//3 2
	for(i = 1; i <= I; i++)
		cin>>x[i];
}
void Output()
{
	int i, j;
	cout<<"输出f[i][j]"<<endl;
	//f[i][j]表示第j个站是在装配线i上完成的,完成1到j的装配最少需要的时间
	for(i = 1; i <= I; i++)
	{
		for(j = 1; j <= J; j++)
			cout<<f[i][j]<<' ';
		cout<<endl;
	}
	cout<<"输出l[i][j]"<<endl;
	//l[i][j]表示使得f[i][j]最小时在哪个装配线上装配j-1
	for(i = 2; i <= I; i++)
	{
		for(j = 1; j <= J; j++)
			cout<<l[i][j]<<' ';
		cout<<endl;
	}
}
int main()
{
	Input();
	Output();
	Fastest_Way();
	Print_Stations();
	Print_Stations2(rl, J);
	return 0;
}


15.2矩阵链乘法

#include <iostream>
using namespace std;

#define N 6
int m[N+1][N+1] = {0}, s[N+1][N+1] = {0};
void Matrix_Chain_Order(int *p)
{
	int i, l, j, k, q;
	for(i = 1; i <= N; i++)
		m[i][i] = 0;
	for(l = 2; l <= N; l++)
	{
		for(i = 1; i <= N - l + 1; i++)
		{
			j = i + l - 1;
			m[i][j] = 0x7fffffff;
			for(k = i; k <= j - 1; 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;
				}
			}
		}
	}
}
//15.2-2递归算法
int Matrix_Chain_Order(int *A, int start, int end)
{
	//只有一个矩阵时,不需要括号
	if(start == end)
		return 0;
	//如果已经有结果,直接使用结果
	if(m[start][end])
		return m[start][end];
	int i, q;
	m[start][end] = 0x7fffffff;
	//P199,公式15.15
	for(i = start; i < end; i++)
	{
		q = Matrix_Chain_Order(A, start, i) + 
			Matrix_Chain_Order(A, i+1, end) + 
			A[start-1] * A[i] * A[end];
		//选最小值
		if(q < m[start][end])
		{
			m[start][end] = q;
			s[start][end] = i;
		}
	}
	return m[start][end];
}
//输出结果
void Print_Optimal_Parens(int *A, int i, int j)
{
	if(i == j)
		cout<<'A'<<i;
	else
	{
		cout<<'(';
		Print_Optimal_Parens(A, i, s[i][j]);
		Print_Optimal_Parens(A, s[i][j]+1, j);
		cout<<")";
	}
}
int main()
{
	int A[N+1] = {5, 10, 3, 12, 5, 50, 6};
	int A2[N+1] = {30, 35, 15, 5, 10, 20, 25};
	Matrix_Chain_Order(A, 1, N);
	Print_Optimal_Parens(A, 1, N);
	return 0;
}


15.3动态规划基础

#include <iostream>
using namespace std;

#define N 6
int m[N+1][N+1] = {0}, s[N+1][N+1] = {0};
//重叠子问题
int Recursive_Matrix_Chain(int *p, int i, int j)
{
	//只有一个矩阵时,不需要括号
	if(i == j)
		return 0;
	//如果已经有结果,直接使用结果
	if(m[i][j])
		return m[i][j];
	int k, q;
	m[i][j] = 0x7fffffff;
	//P199,公式15.15
	for(k = i; k < j; k++)
	{
		q = Recursive_Matrix_Chain(p, i, k) + 
			Recursive_Matrix_Chain(p, 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[i][j];
}
//做备忘录
int Lookup_Chain(int *p, int i, int j)
{
	int k, q;
	if(m[i][j] < 0x7fffffff)
		return m[i][j];
	if(i == j)
		m[i][j] = 0;
	else
	{
		for(k = i; k < j; k++)
		{
			q = Lookup_Chain(p, i, k) + Lookup_Chain(p, 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[i][j];
}
int Medorized_Matrix_Chain(int *p)
{
	int n = N, i, j;
	for(i = 1; i <= n; i++)
	{
		for(j = i; j <= n; j++)
			m[i][j] = 0x7fffffff;
	}
	return Lookup_Chain(p, 1, n);
}
//输出结果
void Print_Optimal_Parens(int *p, int i, int j)
{
	if(i == j)
		cout<<'A'<<i;
	else
	{
		cout<<'(';
		Print_Optimal_Parens(p, i, s[i][j]);
		Print_Optimal_Parens(p, s[i][j]+1, j);
		cout<<")";
	}
}
int main()
{
	int A[N+1] = {5, 10, 3, 12, 5, 50, 6};
	int A2[N+1] = {30, 35, 15, 5, 10, 20, 25};
	//重叠子问题
	memset(s, 0, sizeof(s));
	cout<<Recursive_Matrix_Chain(A, 1, N)<<endl;
	Print_Optimal_Parens(A, 1, N);
	cout<<endl;
	//做备忘录
	memset(s, 0, sizeof(s));
	cout<<Medorized_Matrix_Chain(A)<<endl;
	Print_Optimal_Parens(A, 1, N);
	cout<<endl;
	return 0;
}


15.4最长公共子序列

#include <iostream>
using namespace std;

#define M 8
#define N 9

int b[M+1][N+1] = {0}, c[M+1][N+1] = {0};
int c2[2][M+1] = {0};
/********书上的伪代码*******************************************/
void Lcs_Length(int *x, int *y)
{
	int i, j;
	//初始化
	for(i = 1; i <= M; i++)
		c[i][0] = 0;
	for(j = 1; j <= N; j++)
		c[0][j] = 0;
	//根据公式15.14计算
	for(i = 1; i <= M; i++)
	{
		for(j = 1; j <= N; j++)
		{
			//记录计算结果
			if(x[i] == y[j])
			{
				c[i][j] = c[i-1][j-1] + 1;
				b[i][j] = 2;
			}
			else
			{
				if(c[i-1][j] >= c[i][j-1])
				{
					c[i][j] = c[i-1][j];
					b[i][j] = 1;
				}
				else
				{
					c[i][j] = c[i][j-1];
					b[i][j] = 3;
				}
			}
		}
	}
}
//输出
void Print_Lcs(int *x, int i, int j)
{
	if(i == 0 || j == 0)
		return;
	if(b[i][j] == 2)
	{
		Print_Lcs(x, i-1, j-1);
		cout<<x[i]<<' ';
	}
	else if(b[i][j] == 1)
		Print_Lcs(x, i-1, j);
	else
		Print_Lcs(x, i, j-1);
}
//15.4-2 不使用表b的情况下计算最LCS并输出
void Lcs_Length2(int *x, int *y)
{
	int i, j;
	//初始化
	for(i = 1; i <= M; i++)
		c[i][0] = 0;
	for(j = 1; j <= N; j++)
		c[0][j] = 0;
	//求LCS的时间没有什么区别,只要把与b有关的去掉就可以了
	for(i = 1; i <= M; i++)
	{
		for(j = 1; j <= N; j++)
		{
			//第一种情况
			if(x[i] == y[j])
				c[i][j] = c[i-1][j-1] + 1;
			else
			{
				//第二种情况
				if(c[i-1][j] >= c[i][j-1])
					c[i][j] = c[i-1][j];
				//第三种情况
				else
					c[i][j] = c[i][j-1];
			}
		}
	}
}
//区别在于输出,根据计算反推出前一个数据,而不是通过查找获得
void Print_Lcs2(int *x, int i, int j)
{
	//递归到初始位置了
	if(i == 0 || j == 0)
		return;
	//三种情况,刚好与Lcs_Length2中的三种情况相对应(不是按顺序对应)
	//第二种情况
	if(c[i][j] == c[i-1][j])
		Print_Lcs2(x, i-1, j);
	//第三种情况
	else if(c[i][j] == c[i][j-1])
		Print_Lcs2(x, i, j-1);
	//第一种情况
	else
	{
		//匹配位置
		Print_Lcs2(x, i-1, j-1);
		cout<<x[i]<<' ';
	}
}
//15.4-3备忘录版本,类似于递归,只是对做过的计算记录下来,不重复计算
//每一次迭代是x[1..m]和y[1..n]的匹配
int Lcs_Length3(int *x, int *y, int m, int n)
{
	//长度为0,肯定匹配为0
	if(m == 0|| n == 0)
		return 0;
	//若已经计算,直接返回结果
	if(c[m][n] != 0)
		return c[m][n];
	//公式15.14的对应
	if(x[m] == y[n])
		c[m][n] = Lcs_Length3(x, y, m-1, n-1) + 1;
	else
	{
		int a = Lcs_Length3(x, y, m-1, n);
		int b = Lcs_Length3(x, y, m, n-1);
		c[m][n] = a > b ? a : b;
	}
	return c[m][n];
}
//15.4-4(1)使用2*min(m,n)及O(1)的额外空间来计算LCS的长度
void Lcs_Length4(int *x, int *y)
{
	int i, j;
	//c2是2*min(M,N)的矩阵,初始化
	memset(c2, 0 ,sizeof(c2));
	//类似于上文的循环,只是i%2代表当前行,(i-1)%2代表上一行,其余内容相似
	for(i = 1; i <= N; i++)
	{
		for(j = 1; j <= M; j++)
		{
			if(y[i] == x[j])
				c2[i%2][j] = c2[(i-1)%2][j-1] + 1;
			else
			{
				if(c2[(i-1)%2][j] >= c2[i%2][j-1])
					c2[i%2][j] = c2[(i-1)%2][j];
				else
					c2[i%2][j] = c2[i%2][j-1];
			}
		}
	}
	//输出结果
	cout<<c2[N%2][M]<<endl;
}
void Lcs_Length5(int *x, int *y)
{
	int i, j, temp = 0;
	memset(c2, 0 ,sizeof(c2));
	for(i = 1; i <= N; i++)
	{
		for(j = 1; j <= M; j++)
		{
			if(y[i] == x[j])
				c2[i%2][j] = c2[(i-1)%2][j-1] + 1;
			else
			{
				if(c2[(i-1)%2][j] >= c2[i%2][j-1])
					c2[i%2][j] = c2[(i-1)%2][j];
				else
					c2[i%2][j] = c2[i%2][j-1];
			}
		}
	}
	cout<<c2[N%2][M]<<endl;
}
void Print()
{
	int i, j;
	for(i = 1; i <= M; i++)
	{
		for(j = 1; j <= N; j++)
			cout<<c[i][j]<<' ';
		cout<<endl;
	}
}
int main()
{
	int x[M+1] = {0,1,0,0,1,0,1,0,1};
	int y[N+1] = {0,0,1,0,1,1,0,1,1,0};
	Lcs_Length(x, y);
//	Print();
	Print_Lcs(x, M, N);
//	Lcs_Length2(x, y);
//	Lcs_Length3(x, y, M, N);
//	Print();
//	Print_Lcs2(x, M, N);
//	Lcs_Length4(x, y);
	return 0;
}


15.5最优二叉查找树

#include <iostream>
using namespace std;

#define N 7

double e[N+2][N+2] = {0};//期望
double w[N+2][N+2] = {0};//概率
int root[N+2][N+2] = {0};//记录树的根结点的位置
/*********调试过程中用于输出中间信息***************************************************/
void PrintE()
{
	int i, j;
	for(i = 1; i <= N+1; i++)
	{
		for(j = 1; j <= N+1; j++)
			cout<<e[i][j]<<' ';
		cout<<endl;
	}
	cout<<endl;
}
void PrintW()
{
	int i, j;
	for(i = 1; i <= N+1; i++)
	{
		for(j = 1; j <= N+1; j++)
			cout<<w[i][j]<<' ';
		cout<<endl;
	}
	cout<<endl;
}
void PrintRoot()
{
	int i, j;
	for(i = 1; i <= N+1; i++)
	{
		for(j = 1; j <= N+1; j++)
			cout<<root[i][j]<<' ';
		cout<<endl;
	}
	cout<<endl;
}
/********书上的伪代码对应的程序******************************************************/
//构造最做树
void Optimal_Bst(double * p, double *q, int n)
{
	int i, j, r ,l;
	double t;
	//初始化。当j=i-1时,只有一个虚拟键d|i-1
	for(i = 1; i <= n+1; i++)
	{
		e[i][i-1] = q[i-1];
		w[i][i-1] = q[i-1];
	}
	//公式15.19
	for(l = 1; l <= n; l++)
	{
		for(i = 1; i <= n-l+1; i++)
		{
			j = i+l-1;
			e[i][j] = 0x7fffffff;
			//公式15.20
			w[i][j] = w[i][j-1] + p[j] + q[j];
			for(r = i; r <= j; r++)
			{
				//公式15.19
				t = e[i][r-1] + e[r+1][j] + w[i][j];
				//取最小值
				if(t < e[i][j])
				{
					e[i][j] = t;
					//记录根结点
					root[i][j] = r;
				}
			}
		}
	}
}
/********练习********************************************/
//15.5-1输出最优二叉查找树
void Construct_Optimal_Best(int start, int end)
{
	//找到根结点
	int r = root[start][end];
	//如果左子树是叶子
	if(r-1 < start)
		cout<<'d'<<r-1<<" is k"<<r<<"'s left child"<<endl;
	//如果左子树不是叶子
	else
	{
		cout<<'k'<<root[start][r-1]<<" is k"<<r<<"'s left child"<<endl;
		//对左子树递归使用Construct_Optimal_Best
		Construct_Optimal_Best(start, r-1);
	}
	//如果右子树是叶子
	if(end < r+1)
		cout<<'d'<<end<<" is k"<<r<<"'s right child"<<endl;
	//如果右子树不是叶子
	else
	{
		cout<<'k'<<root[r+1][end]<<" is k"<<r<<"'s right child"<<endl;
		//对右子树递归使用Construct_Optimal_Best
		Construct_Optimal_Best(r+1, end);
	}
}
int main()
{
	int n = N;
//	double p[N+1] = {0, 0.15, 0.10, 0.05, 0.10, 0.20};
//	double q[N+1] = {0.05, 0.10, 0.05, 0.05, 0.05, 0.10};
	double p[N+1] = {0, 0.04, 0.06, 0.08, 0.02, 0.10, 0.12, 0.14};
	double q[N+1] = {0.06, 0.06, 0.06, 0.06, 0.05, 0.05, 0.05};
	Optimal_Bst(p, q, n);
//	PrintE();
//	PrintW();
//	PrintRoot();
	cout<<'k'<<root[1][N]<<" is root"<<endl;
	Construct_Optimal_Best(1, N);
	return 0;
}


 

三、练习

15.1装配线调度

15.1-1

//15.1-1
void Print_Stations2(int i, int j)
{
	cout<<"顺序输出装配路线"<<endl;
	if(j != n)
		i = l[i][j+1];
	//先输出前面的
	if(j > 1)
		Print_Stations2(i, j-1);
	//再输出当前的
	cout<<"line "<<i<<" station "<<j<<endl;
}


15.1-4

不使用l数组来记录,输出时根据类似L4-L13的比较来计算出前一个station的数据

 

15.2矩阵链乘法

15.2-1

算法:类似于15.3的做备忘录

运行结果:((A1A2)((A3A4)(A5A6)))


15.2-2

//15.2-2递归算法
int Matrix_Chain_Order(int *A, int start, int end)
{
	//只有一个矩阵时,不需要括号
	if(start == end)
		return 0;
	//如果已经有结果,直接使用结果
	if(m[start][end])
		return m[start][end];
	int i, q;
	m[start][end] = 0x7fffffff;
	//P199,公式15.15
	for(i = start; i < end; i++)
	{
		q = Matrix_Chain_Order(A, start, i) + 
			Matrix_Chain_Order(A, i+1, end) + 
			A[start-1] * A[i] * A[end];
		//选最小值
		if(q < m[start][end])
		{
			m[start][end] = q;
			s[start][end] = i;
		}
	}
	return m[start][end];
}


 

15.3动态规划基础

15.3-1

枚举的时间的复杂度是O(4^n)/(n^(3/2))

RECURSIVE-MATRIX-CHAIN的时间复杂度是O(n*3^(n-1))

显然后者更有效

 

15.3-3

解释见1楼

#include <iostream>
using namespace std;

#define N 6
int m[N+1][N+1] = {0}, s[N+1][N+1] = {0};
//15.2-2递归算法
int Matrix_Chain_Order(int *A, int start, int end)
{
	//只有一个矩阵时,不需要括号
	if(start == end)
		return 0;
	//如果已经有结果,直接使用结果
	if(m[start][end])
		return m[start][end];
	int i, q;
	m[start][end] = -1;
	//P199,公式15.15
	for(i = start; i < end; i++)
	{
		q = Matrix_Chain_Order(A, start, i) + 
			Matrix_Chain_Order(A, i+1, end) + 
			A[start-1] * A[i] * A[end];
		//选最小值
		if(q > m[start][end])
		{
			m[start][end] = q;
			s[start][end] = i;
		}
	}
	return m[start][end];
}
//输出结果
void Print_Optimal_Parens(int *A, int i, int j)
{
	if(i == j)
		cout<<'A'<<i;
	else
	{
		cout<<'(';
		Print_Optimal_Parens(A, i, s[i][j]);
		Print_Optimal_Parens(A, s[i][j]+1, j);
		cout<<")";
	}
}
int main()
{
	int A[N+1] = {5, 10, 3, 12, 5, 50, 6};
	int A2[N+1] = {30, 35, 15, 5, 10, 20, 25};
	Matrix_Chain_Order(A, 1, N);
	Print_Optimal_Parens(A, 1, N);
	return 0;
}


 15.4最长公共子序列

15.4-1

1 0 0 1 1 0

15.4-2

//15.4-2 不使用表b的情况下计算最LCS并输出
void Lcs_Length2(int *x, int *y)
{
	int i, j;
	//初始化
	for(i = 1; i <= M; i++)
		c[i][0] = 0;
	for(j = 1; j <= N; j++)
		c[0][j] = 0;
	//求LCS的时间没有什么区别,只要把与b有关的去掉就可以了
	for(i = 1; i <= M; i++)
	{
		for(j = 1; j <= N; j++)
		{
			//第一种情况
			if(x[i] == y[j])
				c[i][j] = c[i-1][j-1] + 1;
			else
			{
				//第二种情况
				if(c[i-1][j] >= c[i][j-1])
					c[i][j] = c[i-1][j];
				//第三种情况
				else
					c[i][j] = c[i][j-1];
			}
		}
	}
}
//区别在于输出,根据计算反推出前一个数据,而不是通过查找获得
void Print_Lcs2(int *x, int i, int j)
{
	//递归到初始位置了
	if(i == 0 || j == 0)
		return;
	//三种情况,刚好与Lcs_Length2中的三种情况相对应(不是按顺序对应)
	//第二种情况
	if(c[i][j] == c[i-1][j])
		Print_Lcs2(x, i-1, j);
	//第三种情况
	else if(c[i][j] == c[i][j-1])
		Print_Lcs2(x, i, j-1);
	//第一种情况
	else
	{
		//匹配位置
		Print_Lcs2(x, i-1, j-1);
		cout<<x[i]<<' ';
	}
}


15.4-3

//15.4-3备忘录版本,类似于递归,只是对做过的计算记录下来,不重复计算
//每一次迭代是x[1..m]和y[1..n]的匹配
int Lcs_Length3(int *x, int *y, int m, int n)
{
	//长度为0,肯定匹配为0
	if(m == 0|| n == 0)
		return 0;
	//若已经计算,直接返回结果
	if(c[m][n] != 0)
		return c[m][n];
	//公式15.14的对应
	if(x[m] == y[n])
		c[m][n] = Lcs_Length3(x, y, m-1, n-1) + 1;
	else
	{
		int a = Lcs_Length3(x, y, m-1, n);
		int b = Lcs_Length3(x, y, m, n-1);
		c[m][n] = a > b ? a : b;
	}
	return c[m][n];
}


15.4-4

(1)使用2*min(m,n)及O(1)的额外空间来计算LCS的长度

因为这里只需要求长度,而不需要求序列,可以只存储需要的内容。每一次的计算c[i][j]只与c[i-1][j]、c[i][j-1]、c[i-1][j-1]有关,所以只保留第i行和第i-1行

//15.4-4(1)使用2*min(m,n)及O(1)的额外空间来计算LCS的长度
void Lcs_Length4(int *x, int *y)
{
	int i, j;
	//c2是2*min(M,N)的矩阵,初始化
	memset(c2, 0 ,sizeof(c2));
	//类似于上文的循环,只是i%2代表当前行,(i-1)%2代表上一行,其余内容相似
	for(i = 1; i <= N; i++)
	{
		for(j = 1; j <= M; j++)
		{
			if(y[i] == x[j])
				c2[i%2][j] = c2[(i-1)%2][j-1] + 1;
			else
			{
				if(c2[(i-1)%2][j] >= c2[i%2][j-1])
					c2[i%2][j] = c2[(i-1)%2][j];
				else
					c2[i%2][j] = c2[i%2][j-1];
			}
		}
	}
	//输出结果
	cout<<c2[N%2][M]<<endl;
}


(2)使用min(m,n)项以及O(1)空间

//15.4-4(2)使用min(m,n)及O(1)的额外空间来计算LCS的长度
void Lcs_Length4(int *x, int *y)
{
	int i, j;
	//c2是min(M,N)的矩阵,初始化
	memset(c2, 0 ,sizeof(c2));
	//类似于上文的循环,只是i%2代表当前行,(i-1)%2代表上一行,其余内容相似
	int t1 = 0, t2;
	for(i = 1; i <= N; i++)
	{
		t2 = c[j];
		for(j = 1; j <= M; j++)
		{
			if(y[i] == x[j])
				c2[j] = t1 + 1;
			else
				c2[j] = max(c2[j], c2[j-1]);
			t1 = t2;
		}
	}
	//输出结果
	cout<<c2[M]<<endl;
}

 

15.4-5

#include <iostream>
#include <string>
using namespace std;

#define N 10
int c[N+1] = {0};//c[i]表示A[1..i]的最长递增子序列
int pre[N+1];//pre[i]表示若要得到A[1..i]的最长递增子序列,i的前一个是哪个
//求最长递增子序列
void Length(int *A)
{
	int i, j;
	//A[i] = max{A[j]+1} if A[j]>A[i] and j<i
	for(i = 1; i <= N; i++)
	{
		//初始化
		c[i] = 0;
		pre[i] = 0;
		for(j = 0; j < i; j++)
		{
			if(A[i] > A[j])
			{
				if(c[j] + 1 > c[i])
				{
					c[i] = c[j]+1;
					pre[i] = j;
				}
			}
		}
	}
	cout<<c[N]<<endl;
}
//若要输出A[1..n]中的最长单调子序列,先输出A[1..pre[n]]中的最长单调子序列
void Print(int *A, int n)
{
	if(pre[n])
		Print(A, pre[n]);
	cout<<A[n]<<' ';
}
int main()
{
	//因为从第开始记数,所以数组中的第一个数不算,只是占一个位置
//	int A[N+1] = {0,1,2,3,4,5,6,7,8,9,10};
	int A[N+1] = {0,11,2,13,4,15,6,17,8,19,10};
	Length(A);
	Print(A, N);
	return 0;
}


 

15.1最优二叉查找树

15.5-1

//15.5-1输出最优二叉查找树
void Construct_Optimal_Best(int start, int end)
{
	//找到根结点
	int r = root[start][end];
	//如果左子树是叶子
	if(r-1 < start)
		cout<<'d'<<r-1<<" is k"<<r<<"'s left child"<<endl;
	//如果左子树不是叶子
	else
	{
		cout<<'k'<<root[start][r-1]<<" is k"<<r<<"'s left child"<<endl;
		//对左子树递归使用Construct_Optimal_Best
		Construct_Optimal_Best(start, r-1);
	}
	//如果右子树是叶子
	if(end < r+1)
		cout<<'d'<<end<<" is k"<<r<<"'s right child"<<endl;
	//如果右子树不是叶子
	else
	{
		cout<<'k'<<root[r+1][end]<<" is k"<<r<<"'s right child"<<endl;
		//对右子树递归使用Construct_Optimal_Best
		Construct_Optimal_Best(r+1, end);
	}
}


15.5-2

k5 is root

k2 is k5's left child

k1 is k2's left child

d0 is k1's left child

d1 is k1's right child

k3 is k2's right child

d2 is k3's left child

k4 is k3's right child

d3 is k4's left child

d4 is k4's right child

k6 is k5's right child

d5 is k6's left child

k7 is k6's right child

d6 is k7's left child

d7 is k7's right child

 

15.5-4

//15.5-4
void Optimal_Bst(double * p, double *q, int n)
{
	int i, j, r ,l;
	double t;
	//初始化。当j=i-1时,只有一个虚拟键d|i-1
	for(i = 1; i <= n+1; i++)
	{
		e[i][i-1] = q[i-1];
		w[i][i-1] = q[i-1];
	}
	//公式15.19
	for(l = 1; l <= n; l++)
	{
		for(i = 1; i <= n-l+1; i++)
		{
			j = i+l-1;
			e[i][j] = 0x7fffffff;
			//公式15.20
			w[i][j] = w[i][j-1] + p[j] + q[j];
			//root[i,j-1] <= root[i,j] <= root[i+1,j]
			for(r = root[i][j-1]; r <= root[i+1][j]; r++)
			{
				//公式15.19
				t = e[i][r-1] + e[r+1][j] + w[i][j];
				//取最小值
				if(t < e[i][j])
				{
					e[i][j] = t;
					//记录根结点
					root[i][j] = r;
				}
			}
		}
	}
}

 

四、思考题

15-1 双调欧几里德旅行商问题

算法导论-15-1-双调欧几里得旅行商问题


15-2 整齐打印

算法导论-15-2-整齐打印


15-3 编辑距离

算法导论-15-3-编辑距离


15-4 计划一个公司聚会

算法导论-15-4-计划一个公司聚会


15-6 在棋盘上的移动

算法导论-15-6-在棋盘上移动


15-7 达到最高效益的调度

算法导论-15-7-达到最高效益的调度

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值