算法设计期末考试(七天冲刺)

目录

动态规划

一、矩阵连乘积问题

二、最长公共子序列(LCS)

 三、最大字段和(连续不断开)

1.计算最大字段和的动态规划算法 

2.计算动态字段和的动态规划算法(负数) 

3.最大字段和最优解

四、最长单调递增子序列(可断开)


动态规划

设计动态规划算法的步骤

(1)找出最优解的性质,并刻画其结构特征。

(2)递归地定义最优值(写出动态规划方程)。

(3)自底向上计算出最优值。

(4)根据计算最优值时得到的信息,构造最优解。

动态规划问题的特征

(1)最优子结构:当问题的最优解包含其子问题的最优解时,则称该问题具有最优子结构。

(2)子问题重叠:有些子问题会被反复计算,动态规划利用该性质,每个子问题只解一次,而后将解保存在一个表格中。

一、矩阵连乘积问题

计算三个矩阵ABC的成绩,由于矩阵乘法的性质,不同计算顺序导致的乘法运算量可能相差悬殊。即(AB)C 和A(BC)的运算量差距很大。

详细解析如下:

https://blog.csdn.net/m0_74316795/article/details/139521336?spm=1001.2014.3001.5502

#include<bits/stdc++.h>
using namespace std;
#define NUM 51
int p[NUM];
int m[NUM][NUM];
int s[NUM][NUM];
int LookupChain(int i,int j)
{
	if(m[i][j]>0)return m[i][j];
	if(i==j)return 0;
	int u = LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
	s[i][j] = i;
	for(int k=i+1;k<j;k++)
	{
		int t = LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
		if(t<u){u=t;s[i][j]=k;}
	}
	m[i][j]=u;
	return u;
}
void TraceBack(int i,int j)
{
	if(i==j)printf("A%d",i);
	else
	{
		printf("(");
		TraceBack(i,s[i][j]);
		TraceBack(s[i][j]+1,j);
		printf(")");
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	int i,temp;
	for(i=0;i<=n-1;i++)
		scanf("%d%d",&p[i],&temp);
	p[n]=temp;
	memset(m,0,sizeof(m));
	LookupChain(1,n);
	printf("%d\n",m[1][n]);
	TraceBack(1,n);
	return 0;
}
input:
6
50 10
10 40
40 30
30 5
5 20
20 15
output:
15750
((A1(A2(A3A4)))(A5A6))

二、最长公共子序列(LCS)

X=(A,B,C,B,D,A,B),Y=(B,D,C,A,B,A),则Z1=(B,C,B,A),Z2=(B,C,A,B),Z3=(B,D,A,B)均属于X,Y的最长公共子序列。

c[i][j]=\left\{\begin{matrix} 0 & & (i=0,j=0) \\ c[i-1][j-1]+1 & & (i,j>0;xi=yi) \\ max{c[i][j-1],c[i-1][j]} &&(i,j>0;xi!=yi) \end{matrix}\right.

数组c用来记录最长公共子序列的长度,数组b记录c[i][j]的值是由哪一个子问题的解得到的。X,Y的最长公共子序列的长度记录在c[m][n]中。

 

for(i=1;i<=m;i++)c[i][0]=0;
for(i=1;i<=n;i++)c[0][i]=0;
0123456
YBDCABA
0X0000000
1A0
2B0
3C0
4B0
5D0
6A0
7B0
//数组c初始化
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]=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;}
		}
	}

有相同元素时左上角+1;↖+1

没有相同元素时:

1.上面的值>=左面,取↑

2.否则取←

i=1,j=1时A与B不同,取上方元素 

0123456
YBDCABA
0X0000000
1A00↑
2B0
3C0
4B0
5D0
6A0
7B0

 

0123456
YBDCABA
0X0000000
1A00↑0↑
2B0
3C0
4B0
5D0
6A0
7B0

 

0123456
YBDCABA
0X0000000
1A00↑0↑0↑
2B0
3C0
4B0
5D0
6A0
7B0

元素相同时,↖+1

0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖
2B0
3C0
4B0
5D0
6A0
7B0

 左>上,取左边元素。

0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖1←
2B0
3C0
4B0
5D0
6A0
7B0
0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖0←1↖
2B0
3C0
4B0
5D0
6A0
7B0

 

0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖0←1↖
2B01↖
3C0
4B0
5D0
6A0
7B0
0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖0←1↖
2B01↖1←1←1↑2↖2←
3C0
4B0
5D0
6A0
7B0

数组c:LCS长度计算结果 

0123456
YBDCABA
0X0000000
1A00↑0↑0↑1↖0←1↖
2B01↖1←1←1↑2↖2←
3C01↑1↑2↖2←2↑2↑
4B01↖1↑2↑2↑3↖3←
5D01↑2↖2↑2↑3↑3↑
6A01↑2↑2↑3↖3↑4↖
7B01↖2↑2↑3↑4↖4↑

 最长公共子序列的构造:

在计算最长子序列长度的时候,我们用b[i][j]记录c[i][j]的值是由哪个子问题的解得到的。

b[i][j]==1↖;表示Xi与Yj的最长公共子序列是由Xi-1和Yj-1的最长公共子序列在尾部加上xi得到的子序列。

b[i][j]==2↑;表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列相同。

b[i][j]==3←;表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同。

void LCS(int i, int j, char x[])
{
	if (i == 0 || j == 0) return;
	if (b[i][j] == 1){LCS(i-1,j-1,x);printf("%c", x[i]);}
	else if (b[i][j] == 2) LCS(i-1,j,x);
	else LCS(i,j-1,x);
}

 完整代码:

#include<bits/stdc++.h>
using namespace std;
#define NUM 105
int c[NUM][NUM];
int b[NUM][NUM];
char x[NUM];
char y[NUM];
void LCSLength(int m,int n,const char x[],char y[])
{
	int i,j;
	for(i=1;i<=m;i++)c[i][0]=0;
	for(i=1;i<=n;i++)c[0][i]=0;
	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]=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;}
		}
	}
}
/*
void LCS(int i, int j, char x[])
{
	if (i == 0 || j == 0) return;
	if (b[i][j] == 1){LCS(i-1,j-1,x);printf("%c", x[i]);}
	else if (b[i][j] == 2) LCS(i-1,j,x);
	else LCS(i,j-1,x);
}
*/
int main()
{
	int m,n;
	char x[NUM] = "ABCBDAB";
	char y[NUM] = "BDCABA";
	m = strlen(x);
	n = strlen(y);
	LCSLength(m,n,x,y);
	for(int i=0;i<=m;i++)
	{
		for(int j=0;j<=n;j++)
		{
			printf("%d",c[i][j]);
		}
		printf("\n");
	}
	printf("%d\n",c[m][n]);
//	LCS(m,n,x);
	return 0;
}

运行结果:

0000000
0000111
0011111
0011222
0111222
0112233
0112333
0112334
4

 三、最大字段和(连续不断开)

给定n个整数(可能有负数)组成的序列a1,a2,...,an,要在这n个数中选取相邻的一段ai,ai+1,...,aj(1<=i<=j<=n),使其和最大,并输出最大的和。当所有整数均为负数时,定义最大字段和为0。 

1.计算最大字段和的动态规划算法 

#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n)
{
	int sum=0;
	int b=0;
	for(int i=1;i<=n;i++)
	{
		if(b>0)b+=a[i];
		else b=a[i];
		if(b>sum)sum=b;
	}
	return sum;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	printf("%d\n",MaxSum(n));
	return 0;
}
input:8
1 -3 7 8 -4 12 -10 6
output:23

2.计算动态字段和的动态规划算法(负数) 

#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n)
{
	int sum=a[1];
	int b=a[1];
	for(int i=2;i<=n;i++)
	{
		if(b>0)b+=a[i];
		else b=a[i];
		if(b>sum)sum=b;
	}
	return sum;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	printf("%d\n",MaxSum(n));
	return 0;
}
/*
input:8
1 -3 7 8 -4 12 -10 6
output:23
*/

3.最大字段和最优解

int MaxSum(int n,int &besti,int &bestj)
{
    int sum=0;
    int b=0;
    int begin=0;//下标起始点从0开始
    for(int i=1;i<=n;i++)
    {
        if(b>0)b+=a[i];//如果当前和大于0继续加
        else {b=a[i];begin=i;};//若当前和小于0,将起点移动到现在i的位置
        if(b>sum)//若当前和b大于sum
        {
            sum=b;//更新最大字段和的值
            besti=begin;//若当前和小于0更新起点
            bestj=i;//更新尾部
        }
    }
    return sum;
}

i12345678
a[i]1-378-412-106
b1-271511231319
sum1171515232323
begin11333333
besti11333333
bestj11345666

 

#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n,int &besti,int &bestj)
{
	int sum=0;
	int b=0;
	int begin=0;
	for(int i=1;i<=n;i++)
	{
		if(b>0)b+=a[i];
		else {b=a[i];begin=i;};
		if(b>sum)
		{
			sum=b;
			besti=begin;
			bestj=i;
		}
	}
	return sum;
}
int main()
{
	int n;
	int besti=0,bestj=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	printf("%d\n",MaxSum(n,besti,bestj));
	printf("From %d to %d",besti,bestj);
	return 0;
}
输入:
8
1 -3 7 8 -4 12 -10 6
输出:
23
From 3 to 6

四、最长单调递增子序列(可断开)

辅助数组b,定义b[i]表示以a[i](1<=i<=n)为结尾的最长递增子序列的长度,max{b[i]}为数组a最长递增子序列的长度。k(表示以a[i]为结束的子序列长度)。

 

\left\{\begin{matrix} b[1] = 1 & &(i=1) \\ b[i] = max {b[k]}+1 &&(1<i<=n) \end{matrix}\right.

 在当前值a[i]前面找一个最大值a[k],则以a[i]为结尾的最长递增子序列的长度就是b[k]+1。

序列L={65,158,170,155,239,300,207,389}

b[1]=1,初始化b = 1是因为单个元素本身就是其递增子序列

  1. 使用两个嵌套循环,外部循环从i = 2n,内部循环从j = 1i-1。对于每个a[i],检查是否比之前的a[j]大,并且当前的k(表示以a[i]为结束的子序列长度)小于b[j]。如果是,则更新kb[j]+1,因为我们可以将a[j]添加到以a[i]结尾的子序列中。

  2. 更新b[i]k的值,同时记录下全局最大值maxa,如果b[i]大于maxa,则更新maxa。for(i=2;i<=n;i++)

    {
        int k=0;
        for(j=1;j<=i-1;j++)//在当前坐标之前找
            if(a[j]<a[i]&&k<b[j])k=b[j];
        b[i]=k+1;
        if(maxa<b[i])maxa=b[i];
    }

以i=2为例子:

for(j=1;j<=i-1;j++)

a[1]<a[2] k=0<b[1]=1,k=1,b[2]=k+1=2

i=3时

for(j=1;j<=2;j++)

a[1]<a[3] k=0<b[1]=1,k=1.

a[2]<a[3] k=1<b[2],k=b[2]=2

b[3]=k+1=3

i=4时

 for(j=1;j<=3;j++)

a[1]<a[4] k=0<b[1]=1

a[2]>a[4]

a[3]>a[4]

b[4]=k+1=2

下标12345678
数组a65158170155239300207389
数组b12324546
k01213435
maxa12324546
#include<bits/stdc++.h>
#define NUM 100
int a[NUM];
using namespace std;
int LIS_n2(int n)
{
	int b[NUM]={0};
	int maxa = 0;
	int i,j;
	b[1] = 1;
	for(i=2;i<=n;i++)
	{
		int k=0;
		for(j=1;j<=i-1;j++)
			if(a[j]<a[i]&&k<b[j])k=b[j];
		b[i]=k+1;
		if(maxa<b[i])maxa=b[i];
	}
	return maxa;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	printf("%d\n",LIS_n2(n));
	return 0;
}
/*
8
65 158 170 155 239 300 207 389
output:6
*/

本博客资料、代码来源于清华大学出版社算法设计与分析,本博客仅用于个人学习,可能存在纰漏,敬请批评指正。

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值