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

一、实验目的

1.加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;

2.提高学生利用课堂所学知识解决实际问题的能力;

3.提高学生综合应用所学知识解决实际问题的能力。

二、实验任务

用动态规划算法实现:

1、矩阵链相乘问题

2、投资问题

3、求解完全背包问题

问题描述:有n 种重量和价值分别为wivi (1≤in )的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和最大的挑选方案,这里每种物品可以挑选任意多件。4、数字三角形

问题描述:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。

4、数字三角形

问题描述:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。

用贪心算法实现:

5、最小生成树问题(Prim算法和Kruskal算法)

设G=(V,E)是一个无向连通网,生成树上各边的权值之和称为该生成树的代价,在G的所有生成树中,代价最小的生成树称为最小生成树(Minimal Spanning Trees)。

6、背包问题

【问题描述】设有编号为1、2、...、n的n个物品,它们的重量分别为w1、w2、...、wn,价值分别为v1、v2、...、vn,其中wi、vi(1≤i≤n)均为正数。

有一个背包可以携带的最大重量不超过W。求解目标:在不超过背包负重的前提下,使背包装入的总价值最大(即效益最大化),与0/1背包问题的区别是,这里的每个物品可以取一部分装入背包。

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

实验设备:惠普Win10电脑

开发工具:Java和python环境下,eclipse和pycharm编程工具

四、实验过程设计(算法思路及描述,代码设计)

一.矩阵相乘问题

基本原理和思路:

1、动态规划的第一步:寻找最优子结构。为方便起见,使用Ai...j表示AiAi+1...Aj的乘积结果矩阵。对于k(i<=k<j), 计算Ai...j所需要的计算量为:Ai...k 和 Ak+1...j 以及二者相乘的代价和。

2、设m[i][j]为Ai...j的最优计算顺序所要花费的代价。则其求解公式为:

if i == j, m[i][j] = 0; //因为只有一个矩阵时计算代码为0,即不需要计算。

m[i][j]=min{m[i][k] + m[k+1][j] + Pi-1PkPj} i<=k<j

3、为了能够输出求解顺序,需要保存区间中的一些分割点。假如Ai...j中的最优分割点为k,则我们使用s[i][j]=k。即在Ai...j中,分别计算Ai...k 和 Ak+1...j 所用的计算开销最小。

4、采用自底向上的表格法。依次求解矩阵长度为2,3,...,n的最优计算顺序。

代码实现如下:

cpp 复制代码
#include <stdio.h>
int m[1002][1002],s[1002][1002];
void matrix_chain(int a[], int n)
{
	int l, i, j, k, tmp;
	for(l=2; l<=n; l++)
	{
		for(i=1; i<=n-l+1; i++)		//长度为l的区间,其最小下标为1~n-l+1
		{
			j=i+l-1;
			m[i][j] = 0x7fffffff;
			for(k=i; k<j; k++)		//i~k, k+1~j, 所以k<j
			{
				tmp = m[i][k]+m[k+1][j]+a[i-1]*a[k]*a[j];
				if(tmp < m[i][j])
				{
					m[i][j] = tmp;
					s[i][j] = k;
				}
			}
		}
	}

}
void print(int i, int j)
{
	if(i == j)
		printf("A%d",i);
	else{
		printf("(");
		print(i, s[i][j]);
		print(s[i][j]+1, j);
		printf(")");
	}
}
int main()
{
	int n, a[1002];
	int i,j,l;
	while(scanf("%d",&n)==1)	//输入有n个矩阵
	{
		for(i=0; i<n+1; i++)
			scanf("%d",&a[i]);
		
		//memset(m, 0x7fffffff,sizeof(m));
		for(i=0; i<n+1; i++)
			m[i][i] = 0;
		matrix_chain(a, n);
		printf("%d\n",m[1][n]);
		print(1, n);
		printf("\n");
	}

	return 0;
}

分析:时间复杂度为O(N3), 我们只需要存储一个矩阵就可以了,所以空间复杂度是 O(N2)。

二.投资问题

基本原理和思路:假设分配给第 i 个项目的钱数是 xi,问题描述为:

目标函数:max{f1(x1)+f2(x2)+...+fn(xn)}

约束条件:x1+x2+...+xn=m,xi∈N;设Fk(x)表示x元投给前k个项目的最大效益,k=1,2,...,n,x=1,2,...,m递推方程:Fk(x)=max{fk(xk)+Fk-1(x-xk)}(0≤xk≤x),k=2,3,...,n边界条件:F1(x)=f1(x),Fk(0)=0,k=1,2,...,n*说明:第k步,前后共分配x万元,分配给第k个项目xk;x-xk万元,分配给前k-1个项目;

代码实现如下:

cpp 复制代码
#include <iostream>
#include <vector>

using namespace std;

int main() {

	int m, n;//m元钱,n项投资
	int i, j;
	int tmp_m, tmp_F = 0;
	cout << "请输入投资金额和项目数" << endl;
	cin >> m >> n;
	vector<vector<int>> f(n, vector<int>(m + 1));//f[i][x], 0<i<=n,0<=x<=m;
	vector<vector<int>> F(n, vector<int>(m + 1));//F[i][x],将x元钱投入到前i个项目上最大的收益
	
	//在第(i+1)个项目上投入0元,收益为0,注意i从0开始
	for (i = 0; i < n; i++) {
		f[i][0] = 0;
	}
	cout << "请输入各项目对应投资金额的收益(从1开始)" << endl;
	for (i = 0; i < n; i++) {
		for (j = 1; j < m + 1; j++) {
			cin >> f[i][j];
		}
	}
	//初始化,给F[0][0-m]赋值
	for (j = 0; j < m + 1; j++) {
		F[0][j] = f[0][j];//第一个项目上投入0-m元钱的最大收益等于f[0][0-m]
	}

	for (i = 1; i < n; i++) {//项目编号,从1开始
		for (j = 0; j < m + 1; j++) {//钱数,从0开始
			for (tmp_m = 0; tmp_m <= j; tmp_m++) {
				//递推公式
				tmp_F = F[i - 1][j - tmp_m] + f[i][tmp_m];
				//取最大值
				if (tmp_F > F[i][j]) {
					F[i][j] = tmp_F;
				}	
			}
		}
	}
	cout << "最大总收益: " << F[n - 1][m] << endl;
}

分析:复杂度 W(n,m)=O(nm2)。

三. 求解完全背包问题

基本原理和思路:1.设置动态规划二维数组dp,dp[i][j]表示从前i个物品中选出重量不超过j(或者剩余容量为j)的物品的最大总价值。

①显然有边界条件:dp[i][0]=0(背包不能装入任何物品时,总价值为0),dp[0][j]=0(没有任何物品可装入时,总价值为0),可以采用memset函数一次性初始化为0.

②另外设置二维数组fk,其中fk[i][j]存放dp[i][j]得到最大值时物品i挑选的件数。

代码实现如下:

cpp 复制代码
//求解完全背包问题的算法
#include <stdio.h>
#include <string.h>
#define MAXN 20				//最多物品数
#define MAXW 100			//最大限制重量
#define max(x,y) ((x)>(y)?(x):(y))
//问题表示
int n,W;
int w[MAXN],v[MAXN];
//求解结果表示
int dp[MAXN+1][MAXW+1],fk[MAXN+1][MAXW+1];
int solve()					//求解多重背包问题
{
	int i,j,k;
	for (i=1;i<=n;i++)
	{
		for (j=0;j<=W;j++)
			for (k=0;k*w[i]<=j;k++)
			{
				if (dp[i][j]<dp[i-1][j-k*w[i]]+k*v[i])
				{
					dp[i][j]=dp[i-1][j-k*w[i]]+k*v[i];
					fk[i][j]=k;		//物品i取k件
				}									
			}
	}
	return dp[n][W];
}
void Traceback()				//回推求最优解
{
	int i=n,j=W;
	while (i>=1)
	{
		printf("物品%d共%d件 ",i,fk[i][j]);
		j-=fk[i][j]*w[i];		//剩余重量
		i--;
	}
	printf("\n");
}
void prin(){   //查看dp数组与fk数组 
	int i,j,k;
	printf("fk[i][j]:\n");
	for (i=1;i<=n;i++)
	{
		for (j=0;j<=W;j++){
			printf("%d ",fk[i][j]);
		}
		printf("\n");
	}
	printf("dp[i][j]:\n");
	for (i=1;i<=n;i++)
	{
		for (j=0;j<=W;j++){
			printf("%d ",dp[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	w[1]=3; w[2]=2; w[3]=6;w[4]=2;
	v[1]=7; v[2]=2; v[3]=5;v[4]=3;
	n=4; W=9;
	memset(dp,0,sizeof(dp));
	memset(fk,0,sizeof(fk));
	printf("最优解:\n");
	printf("  总价值=%d\n",solve());
	printf("  方案: ");Traceback();
	printf("\n");
	prin();
	return 0;
} 

分析:空间复杂度O(nV) 时间复杂度O(nV)。

四.数字三角形

基本原理和思路:MaxSum(i,j):从第i行j列到底边的最大数字之和

从最后一行开始递推,MaxSum(n,j)=D(n,j)//n行j列,MaxSum(n-1,j) = D(n-1,j) + max( MaxSum(n,j) , MaxSum(n,j+1) )

然后为了减少空间,不需要用二维数组来存储MaxSum(n,j)的值,只需要求MaxSum(n,j)的时候存储下一行MaxSum(n+1,j)的值就可以,然后计算完第n行的MaxSum之后再覆盖原来的第n+1行的MaxSum的值。

代码实现如下:

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
#define Max 101
int D[Max][Max];
int n;
int maxSum[Max][Max];
int MaxSum(int i,int j)
{
    if(maxSum[i][j]!=-1)
        return maxSum[i][j];
    if(i==n)
        maxSum[i][j]=D[i][j];
    else
    {
        int x=MaxSum(i+1,j);
        int y=MaxSum(i+1,j+1);
        maxSum[i][j]=max(x,y)+D[i][j];
    }
    return maxSum[i][j];
}
int main()
{
    int i,j;
    cin>>n;
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
    {
        cin>>D[i][j];
        maxSum[i][j]=-1;
    }
    cout<<MaxSum(1,1)<<endl;
    return 0;
}

分析:时间复杂度是n2。

五.最小生成树问题

prim算法

基本原理和思路:设G=(V, E)是具有n个顶点的连通网,

T=(U, TE)是G的最小生成树,

T的初始状态为U={u0}(u0∈V),TE={ },

重复执行下述操作:

在所有u∈U,v∈V-U的边中找一条代价最小的边(u, v)并入集合TE,同时v并入U,直至U=V。

数组lowcost[n]:用来保存集合V-U中各顶点与集合U中顶点最短边的权值,lowcost[v]=0表示顶点v已加入最小生成树中;

数组adjvex[n]:用来保存该边所依附的(集合V-U中各顶点与集合U中顶点的最短边)集合U中的顶点。

代码实现如下:

cpp 复制代码
void prime(MGraph G){
    for(int i=1;i<G.vertexNu;i++){
        lowcost[i]=G.arc[0][i];  adjvex[i]=0;
    }
    lowcost[0]=0;
    for(i=1;i<G.vertexNum;i+++){
        k=MinEdge(lowcost,G.vertexNum)
        cout<<K<<adjvex[k]<<lowcost[k];
        lowcost[k]=0;
        for(j=1;j<G.vertexNum;j++)
          if((G.arc[k][j]<lowcost[j]){
              lowcost[j]=G.arc[k][j];
              arcvex[j]=k;
           }
}

六.背包问题

基本原理和思路:利用动态规划思想 ,子问题为:f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。其状态转移方程是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]} //这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。将前i件物品放入容量为v的背包中"这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前i-1件物品相关的问题。

1.如果不放第i件物品,那么问题就转化为"前i-1件物品放入容量为v的背包中",价值为f[i-1; v];

2.如果放第i件物品,那么问题就转化为"前i-1件物品放入剩下的容量为

v-Ci的背包中",此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值Wi

代码实现如下:

cpp 复制代码
int n = 5;
double W = 100;
struct NodeType
{
	double w;
	double v;
	double p;
	bool operator<(const NodeType &s)const
	{
		return p > s.p;
	}
};

NodeType A[] = { {0},{10,20},{20,30},{30,66},{40,40},{50,60} };
double V;
double x[MAXN];

void Knap()
{
	V = 0;
	double weight = W;
	memset(x, 0, sizeof(x));
	int i = 1;

	while (A[i].w < weight)//物品可以全部装入
	{
		x[i] = 1;
		weight -= A[i].w;
		V += A[i].v;
		i++;
	}

	if (weight > 0)//余下物品重量大于0
	{
		x[i] = weight / A[i].w;
		V += x[i] * A[i].v;
	}
}

分析:时间复杂度为O(nlog2n)

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

经过这次试验收获颇多,代码实现过程中也遇到一些问题,有的问题确实也有一定的难度,所以也是通过了网络搜索才得出的解决方案,思维上得到了很好的训练,同时也明白了一个道理:纸上得来终觉浅,绝知此事要躬行。尤其是算法和编程这门课程更是要勤于动手方能获得收获。希望下次实验或者之后的编程学习能吸取这些教训。

相关推荐
醉颜凉1 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
hunandede1 小时前
FFmpeg 4.3 音视频-多路H265监控录放C++开发十三.2:avpacket中包含多个 NALU如何解析头部分析
c++·ffmpeg·音视频
lapiii3581 小时前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
取个名字真难呐1 小时前
矩阵乘法实现获取第i行,第j列值,矩阵大小不变
python·线性代数·矩阵·numpy
爱学习的大牛1231 小时前
通过vmware虚拟机安装和调试编译好的 ReactOS
c++·windows内核
Dontla2 小时前
Rust泛型系统类型推导原理(Rust类型推导、泛型类型推导、泛型推导)为什么在某些情况必须手动添加泛型特征约束?(泛型trait约束)
开发语言·算法·rust
Ttang232 小时前
Leetcode:118. 杨辉三角——Java数学法求解
算法·leetcode
喜欢打篮球的普通人2 小时前
rust模式和匹配
java·算法·rust
java小吕布2 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法