算法设计与分析实验报告c++实现(矩阵链相乘问题、投资问题、背包问题、TSP问题、数字三角形)

本文介绍了如何运用动态规划解决矩阵链相乘、投资问题、背包问题、旅行商问题(TSP)和数字三角形问题,通过实例展示了算法设计步骤、计算过程以及各问题的时间复杂度分析,加深了对动态规划解决实际问题的理解和掌握。
摘要由CSDN通过智能技术生成

一、实验目的

1.加深学生对动态规划算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。

二、实验任务

1、矩阵链相乘问题

image-20240404002318769
2、投资问题

image-20240404002332398

3、背包问题

img

4、TSP问题
旅行家要旅行n个城市,要求经历各个城市且仅经历一次,然后回到出发城市,并要求所走的路程最短。
5、数字三角形
问题描述:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。

image-20240404002353934

三、实验设备及编程开发工具

编程开发工具:Microsoft Visual c++

四、实验过程设计(算法设计过程)

(一)、矩阵链相乘问题

1 动态规划的4个步骤为:
(1).寻找一个最优子结构。
对于原问题,我们使用表示乘积的结果矩阵,如果我们将在处进行一次括号划分,假设将其划分为和两部分,则=++(两个划分矩阵合并的代价)。假设这样划分是最优的,则只需继续在和上继续取到最佳划分即可。
(2). 递归地定义最优解的值
在原问题中,我们定义数组m[i][j]表示计算矩阵所需乘法的最小值,则原问题计算所需最小代价为m[1][n]。
如果k表示矩阵i到j之间的划分点,那么m数组的公式为:

20150506001233277 (3).计算最优代价
如果直接递归计算的话,我们会发现计算量仍然会很大,并没有明显改善。因此在这里,我们使用自底向上的方法进行计算,并且在计算中保存已经计算过的值,这样当上一层划分计算时,直接调用下层之前计算保存过的值即可。
在这整个过程中,我们使用m[i][j]从矩阵i到j的最小代价,用s[i][j]保存最小代价时的划分位置。根据2步骤的公式,我们只需按链条长度递增的顺序进行求解即可。在这里,它的长度是从2到n的(长度为1时直接为0)。由于只知道链的长度,因此有多个乘法问题,因此我们需要求解出所有乘法问题的最小代价。
(4).构造最优解
2 源程序

#include<stdio.h>
#include<string.h>
#define N 1000
int m[N][N];//m[i][j]表示从第i个矩阵乘到第j个矩阵所需的最小代价
int s[N][N];  //s[i][j]表示最优值m[i][j]对应的分割点
int p[N];     //p[]代表矩阵链

void MATRIX_CHAIN_ORDER(int p[], int n) //对矩阵链p求解最佳组合方法
{
    for(int i = 1; i <= n; i++) //子问题链长1时代价为0
        m[i][i] = 0;
    //对子问题链长2到n从小到大求出代价
    for(int length = 2; length <= n; length++) //枚举子问题链长
    {
        //在对应链长下求出所有情况
        for(int i = 1; i <= n-length+1; i++)  //所有情况的开始位置
        {
            int j = i+length-1;
            m[i][j] = 999999;   //此时求出从i到j的最小代价m[i][j]=min{m[i][k]+m[k+1][j]+Pi-1*Pk*Pj}
            for(int k = i; k <= j-1; k++)
            {
                if(m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] < m[i][j])
                {
                    m[i][j] = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                    s[i][j] = k;
                }
            }
        }
    }
}

void PRINT_OPTIMAL_PARENS(int s[][N], int start, int ends)//利用表s输出从start乘到ends的最优方案
{
    if(start == ends)
        printf("A[%d]", start);
    else
    {
        printf("(");
        PRINT_OPTIMAL_PARENS(s, start, s[start][ends]);
        PRINT_OPTIMAL_PARENS(s, s[start][ends]+1, ends);
        printf(")");
    }
}

int main()
{
    int number;
    printf("请输入待乘矩阵的个数:\n");
    while(scanf("%d",&number) && number>0)
    {
        memset(m, 0, sizeof(m));
        memset(p, 0, sizeof(p));
        printf("请输入矩阵链:\n");
        for(int i = 0; i <= number; i++)
            scanf("%d", &p[i]);
        MATRIX_CHAIN_ORDER(p, number);
        PRINT_OPTIMAL_PARENS(s, 1, number);
        printf("\n");
        printf("请输入待乘矩阵的个数:\n");
    }
    return 0;
}

(二)、投资问题

1 算法分析
根据题目要求的项目个数,将解题思路分为项目数个阶段:
第一阶段:只考虑第一个项目,即将所有的资金都投资给该项目,那么此时可以获取的利益可以计算出来;
第二阶段:将第二个项目加进去,即投资的资金可以同时分配给两个项目,那么此时可以获取的利益可以计算出来;
第三阶段:将第三个项目加进去,即投资的资金可以同时分配给三个项目,同样可以用上述方法得到此时能获取的利益。
后面以此类推。
2 源程序

 #include<stdio.h> 
#include<conio.h>
 void main() 
{      
void jie(int,int,int d[][6]);     
void Invest(int m,int n,int f[][6],int g[][6],int d[][6]);    
int m=5,n=4,f[5][6],d[5][6];   
int g[5][6]={{0},{0,11,12,13,14,15},
{0,0,5,10,15,20},{0,2,10,30,32,40},{0,20,21,22,23,24}};     
Invest(m,n,f,g,d);     
printf("可获得的最大收益为:%d\n",f[4][5]);     
jie(m,n,d); 
 
} 
 void Invest(int m,int n,int f[][6],int g[][6],int d[][6]) 
{   
int i,j,k,s;  
for(j=0;j<=m;j++)       
{    
f[1][j]=g[1][j];d[1][j]=j;}
           for(i=2;i<=n;i++)               
 for(j=0;j<=m;j++)                  
{    f[i][j]=0;                      
for(k=0;k<=j;k++)                      
 { 
s=f[i-1][j-k]+g[i][k];                           if(s>f[i][j])     
{   
f[i][j]=s;  d[i][j]=k;  
}                     
  }               
 }    
}  

void jie(int m,int n,int d[][6])
{  
int s=m;  int k[5];  
int i;  
k[n]=d[n][m];  
for(i=n-1;i>0;i--)  
{        
 s = s-k[i+1];        
k[i] = d[i][s];  
}  

for(i=1;i<=4;i++)   
printf("%5d",k[i]);  
printf("\n");    
 getch();
 }         

(三)、背包问题

1 算法过程

image-20240404002628533

image-20240404002652707

image-20240404002709939

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

const int N = 150;
char t[N] = {'A','B','C','D'};
int v[N] = { 0, 12, 8, 9, 5 };
int w[N] = { 0, 15, 10, 12, 8 };
int x[N];
int m[N][N];
int c = 30;
int n = 4;
void traceback()
{
	for (int i = n; i>1; i--)
	{
		if (m[i][c] == m[i - 1][c])
			x[i] = 0;
		else
		{
			x[i] = 1;
			c -= w[i];
		}
	}
	x[1] = (m[1][c]>0) ? 1 : 0;
}

int main()
{
	memset(m, 0, sizeof(m));
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= c; j++)
		{
			if (j >= w[i])
			if (m[i - 1][j]> m[i - 1][j - w[i]] + v[i])
				m[i][j] = m[i - 1][j];
			else 
				m[i][j] = m[i - 1][j - w[i]] + v[i];
			else
				m[i][j] = m[i - 1][j];
		}
	}/*
	 for(int i=1;i<=6;i++)
	 {
	 for(int j=1;j<=c;j++)
	 {
	 cout<<m[i][j]<<' ';
	 }
	 cout<<endl;
	 }
	 */
		printf("项目  ");
		for (int q = 0; q < 4; q++)
		{
			printf("   %c",t[q]);
		}
		
		printf("\n");
		printf("投资额");

		for (int q = 1; q < 5; q++)
		{
			printf("%4d", v[q]);
		}
		
		printf("\n");
		printf("收益  ");
	
		for (int q = 1; q < 5; q++)
		{
			printf("%4d", w[q]);
		}
		printf("\n");
	traceback();
	cout << "项目选择结果是:" ;
	for (int i = 1; i <= n; i++)
		cout <<"   "<< x[i];
		cout << endl;
	system("pause");
	return 0;
}

(四)TSP问题

1 动态规划解决方法
假定我们从城市1出发,经过了一些地方,并到达了城市j。毋庸置疑,我们需要记录的信息有当前的城市j。同时我们还需要记录已经走过的城市的集合。同理,使用S记录未走过的城市的集合也可以的,且运算方便。
于是我们可以得出状态转移方程 go(S,init)=min{go(S−i,i)+dp[i][init]}∀s∈Sgo(S,init)=min{go(S−i,i)+dp[i][init]}∀s∈S go(s,init)表示从init点开始,要经过s集合中的所有点的距离
因为是NP问题,所以时间复杂度通常比较大。使用dis[s][init]用来去重,初始化为-1,如果已经计算过init—>S(递归形式),则直接返回即可.
2 源程序

#include<iostream>
#include<cmath> 
#include<iomanip>
using namespace std;
int s;
int N;//点的个数 
int init_point;
double x[20];
double y[20];
double dp[20][20];//两个城市的距离 
double dis[1048577][20];//2^20=1048576 表示出发点到S集合是否已经访问过 
double go(int s,int init)
{
    if(dis[s][init]!=-1) return dis[s][init];//去重 
    if(s==(1<<(N-1))) return dp[N-1][init];//只有最后一个点返回 
    double minlen=100000;
    for(int i=0;i<N-1;i++)//只能在1到n-2点中查找 
    {
        if(s&(1<<i))//如果i点在s中且不为发出点 
        {
            if(go(s&(~(1<<i)),i)+dp[i][init]<minlen)
                minlen=go(s&(~(1<<i)),i)+dp[i][init];
        } 
    } 
    return dis[s][init]=minlen;
}
int main()
{
    int T;
    cin>>T;
    while(T--)//测试样例数 
    {
        cin>>N;
        for(int i=0;i<N;i++)
            cin>>x[i]>>y[i];//读入城市的坐标 
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
            {
                dp[i][j]=sqrt(pow((x[i]-x[j]),2)+pow((y[i]-y[j]),2));
                //计算两个城市的距离 
            }   

        for(int i=0;i<pow(2,N);i++)
            for(int j=0;j<N;j++)
                dis[i][j]=-1;//去重数组初始化  
        init_point=0;
        s=0; 
        for(int i=1;i<N;i++)
            s=s|(1<<i);//从1开始,保证初始点没有在S里面           
        double distance=go(s,init_point);
        cout<<fixed<<setprecision(2)<<distance<<endl;

    }       
} 

(五)数字三角形

1 动态规划解决问题
设 d( i , j )表示数字三角形中的第 i 行第 j 个点。 max[i][j]表示 第 i 行 第 j 个数字到低端的最佳路径之和,则原问题的解就是 max[1][1] 的值了。
  从d( i,j )这个点向下走,显然只能走 d( i+1,j ) 和 d( i+1 ,j+1 ) 这两个点了。
  而 max[i][j] 的最优值= d( i,j) 的值 + max{ max[i+1][j] ,max[i+1][j+1] }。
  所以,我们可以至底向上来计算。先计算最后一层的点的,然后倒二层的,一直算到第一层。
2 源程序

#include <stdlib.h>
int main()
{
	int n, a[101][101], d[101][101], i, j, k;
	scanf_s("%d", &n);
	for (i = 1; i <= n; i++)
	for (j = 1; j <= i; j++)
		scanf_s("%d", &d[i][j]);
	for (j = 1; j <= n; j++)
		a[n][j] = d[n][j];//从最后一行开始;
	for (i = n - 1; i >= 1; i--)
	for (j = 1; j <= i; j++)
	{
		if (a[i + 1][j + 1]>a[i + 1][j]) a[i][j] = d[i][j] + a[i + 1][j + 1];
		else
			a[i][j] = d[i][j] + a[i + 1][j];
	}
	printf("%d\n", a[1][1]);
	system("pause");
	return 0;
}

五、实验结果及算法复杂度分析

(一) 矩阵链相乘问题
1 实验结果

image-20240404002917707

2 算法复杂度分析
计算时间复杂度为:
O ( 1 + 1 + 2 + … + 1 + 2 + … + ( N − 1 ) ) = O ( ∑ n = 1 N − 1 ∑ r = 1 n r ) = O ( ∑ n = 1 N − 1 ( n + 1 ) n 2 ) = O ( ∑ n = 1 N − 1 n 2 + ∑ n = 1 N − 1 n ) = O ( N 3 ) O({1}+{1+2}+…+{1+2+…+(N−1)})\\ =O(∑n=1N−1∑r=1nr)\\ =O(∑n=1N−1(n+1)n2)\\ =O(∑n=1N−1n2+∑n=1N−1n)\\ =O(N3) O(1+1+2++1+2++(N1))=O(n=1N1r=1nr)=O(n=1N1(n+1)n2)=O(n=1N1n2+n=1N1n)=O(N3)

(二)投资最少问题
1 实验结果

image-20240404002951664

2 算法复杂度分析
时间复杂度为O(nC),把一个多维决策问题转化为多个一维最优化问题,能求出全局极大或者极小,但是缺点在于空间占据过多。

(三)背包问题
1 实验结果

image-20240404003004793

2 算法复杂度分析
时间复杂度为O(n),每次循环都需要进行比较,

(四)TSP问题
1 实验结果

image-20240404003016497

2 算法复杂度

img

(五) 数字三角形
1 实验结果

image-20240404003031183

2 算法复杂度
动态规划将计算过的数值保存,再次要用的时候直接取就可以大大减少计算次数,得出时间复杂度就是 O ( n ) = n 2 O(n)=n^2 On=n2

实验小结(包括问题和解决方法、心得体会等)

通过实现动态规划的这些题目,对动态规划算法有了进一步的了解,先分析问题,判断是否具有最优子结果和重叠字问题的性质。动态规划算法是由单阶段的决策最优逐步转化为多阶段的决策最优,最后构造一个最优解。经过反复的调试操作,程序运行才得出结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温柔说给风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值