ZUFE算法课

实验平台:Visual studio 6.0 或者VS2008及以上
编程语言:C语言或者C++语言

课程实验1 递归与分治策略

1.采用递归算法实现正整数10以内阶乘,并按照以下的顺序进行输出。

算法输入:10以内的正整数
算法输出:输入正整数的阶乘

阶乘函数可递归地定义为:
在这里插入图片描述

输出结果形式:
0!=1
1!=1
2!= 2
3!= 6
4!= 24
5!= 120
6!= 720
7!= 5040
8!= 40320
9!= 362880
10!= 3628800

#include<bits/stdc++.h>
using namespace std;
int factorial(int n);
int main() {
	for(int i=0; i<=10; i++) {
		cout<<i<<"!="<<factorial(i)<<endl;
	}
	return 0;
}
int factorial(int n) {
	if(n==0) return 1;
	else return n*factorial(n-1);
}

2. 采用递归算法实现Ackerman函数,并且计算n, m小于等于3的输出结果。

算法输入:n与m
算法输出:Ackerman函数的结果,即A (n, m)

Ackerman函数A(n,m)定义如下:
在这里插入图片描述

输出结果形式:
A(0,0)=1 A(0,1)=1 A(0,2)=1 A(0,3)=1
A(1,0)=2 A(1,1)=2 A(1,2)=2 A(1,3)=2
A(2,0)=4 A(2,1)=4 A(2,2)=4 A(2,3)=4
A(3,0)=5 A(3,1)=6 A(3,2)=8 A(3,3)=16

#include<bits/stdc++.h>
using namespace std;
int Ackerman(int n,int m);
int main(){
	for(int i=0;i<4;i++){
		for(int k=0;k<4;k++){
			printf("A(%d,%d)=%d ",i,k,Ackerman(i,k));
		}
		cout<<endl;
	}
	return 0;
} 
int Ackerman(int n,int m){
	if(n==1&&m==0) return 2;
	else if(n==0&&m>=0) return 1;
	else if(n>=2&&m==0) return n+2;
	else if(n>=1&&m>=1) return Ackerman(Ackerman(n-1,m),m-1);
}

3. 采用递归算法实现Hanoi塔问题

设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座b上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:
规则1:每次只能移动1个圆盘;
规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;
规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上。

算法输入:圆盘的个数n,以及a,b,c三个塔座
算法输出:移动的规则顺序

//a为起始柱,b为目标柱,c为辅助柱,hanio表示将n个圆盘从a移到b。
//那只要把n-1移到c,最大的那个移到b,再把c上的都移到b就解决问题;
//则问题就转化为:
//将n-1个圆盘从a移到c,这个时候圆盘数量为n-1,起始柱为a,目标柱为c,辅助柱为b;
//然后将最大的圆盘从起始柱a移到b;
//然后将n-1个圆盘从c移到b,起始柱为c,目标柱为b,辅助柱位a;
#include<bits/stdc++.h>
using namespace std;
int n;
void hanio(int n,char a,char b,char c);
int main() {
	while(cin>>n) {
		hanio(n,'a','b','c');
	}
	return 0;
}
void hanio(int n,char a,char b,char c) {
	if(n==1) cout<<a<<"-->"<<b<<endl;
	else {
		hanio(n-1,a,c,b);
		cout<<a<<"-->"<<b<<endl;
		hanio(n-1,c,b,a);
	}
}

课程实验2 动态规划

实例1 矩阵连乘问题

问题描述:给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2 ,…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。例如:

A1={30x35} ;A2={35x15} ;A3={15x5} ;A4={5x10} ;A5={10x20} ;A6={20x25} ;

最后的结果为:((A1(A2A3))((A4A5)A6)) 最小的乘次为15125。

解题思路:能用动态规划的一个性质就是最优子结构性质,也就是说计算A[i:j]的最优次序所包含的计算矩阵子琏A[i:k]和A[k+1:j]的次序也是最优的。动态规划算法解此问题,可依据其递归式以自底向上的方式进行计算(即先从最小的开始计算)。在计算过程中,保存已解决的子问题答案。每个子问题只计算一次,而在后面需要时只要简单查一下,从而避免大量的重复计算,最终得到多项式时间的算法。我们可以根据下面这个公式来计算结果。其中p[i-1]表示的是第i个矩阵的行数,p[k]表示i:k矩阵合起来后最后得到的列数,p[j]是k+1:j合起来后得到的列数。这个部分的计算方法其实就是计算两个矩阵相乘时总共的乘次数
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+7;
int n,p[maxn],ans[maxn][maxn];
int main() {
	cin>>n;
	memset(ans,0,sizeof(ans));
	for(int i=0; i<=n; i++)
		cin>>p[i];
	for(int len=2; len<=n; len++) {//枚举长度
		for(int i=1; i<=n+len-1; i++) {//枚举起点
			ans[i][i+len-1]=ans[i][i]+ans[i+1][i+len-1]+p[i-1]*p[i]*p[i+len-1];
			for(int k=i+1; k<=i+len-1; k++)//枚举断点 
				ans[i][i+len-1]=min(ans[i][i+len-1],
				                    ans[i][k]+ans[k+1][i+len-1]+p[i-1]*p[k]*p[i+len-1]);
			}
	}
	cout<<ans[1][n]<<endl;
	return 0;
}

实例2 剪绳子问题

问题描述:给你一根长度为n的绳子,请把绳子剪成m段(m,n都是整数),每段绳子的 长度记为k[0],k[1],k[2]…. 请问如何剪绳子使得k[0],k[1],k[2] 的乘积最大
例如 绳子长度n=8 最大乘积18 = 233 剪成了m=3段。

思路:采用自底向上的动态规划方法。设f(n)代表长度为n的绳子剪成若干段的最大乘积,如果第一刀下去,第一段长度是i,那么剩下的就需要剪n-i,那么f(n)=max{f(i)f(n-i)}。而f(n)的最优解对应着f(i)和f(n-i)的最优解,假如f(i)不是最优解,那么其最优解和f(n-i)乘积肯定大于f(n)的最优解,和f(n)达到最优解矛盾,所以f(n)的最优解对应着f(i)和f(n-i)的最优解。
首先,剪绳子是最优解问题,其次,大问题包含小问题,并且大问题的最优解包含着小问题的最优解,所以可以使用动态规划求解问题,并且从小到大求解,把小问题的最优解记录在数组中,求大问题最优解时就可以直接获取,避免重复计算。
n<2时,由于每次至少剪一次,所以返回0。n=2时,只能剪成两个1,那么返回1。n=3时,可以剪成3个1,或者1和2,那么最大乘积是2。当n>3时,就可以使用公式进行求解。
f(4)=max{f(1)f(3), f(2)f(2)}
f(5)=max{f(1)f(4), f(2)f(3)}

f(n)=max{f(1)f(n-1), f(2)f(n-2), f(3)f(n-3), …, f(i)(fn-i), …} (0<i<=n/2)

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+7;
int n,m,line[maxm],t[maxm],ans;
int f(int x);
int main() {
	while(cin>>n) {
		ans=f(n);
		cout<<"乘积最大为:"<<ans<<endl;
		if(n==1)
			m=1;
		else if(n>=2&&n<=7)
			m=2;
		else
			m=2+(n-7)/3+1;
		cout<<"段数为:"<<m<<endl;
	}
	return 0;
}
int f(int x) {
	if(x==1)
		return 0;
	if(x==2)
		return 1;
	if(x==3)
		return 2;
	line[1]=1;
	line[2]=2;
	line[3]=3;
	for(int k=4; k<=x; k++) {
		for(int i=1; i<=k/2; i++) {
			line[k]=max(line[k],line[i]*line[k-i]);
		}
	}
	return line[x];
}

实例3 国王和金矿

问题描述:有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

提示:我们将金矿设为N,工人数设为W,金矿的黄金量设为数组G,金矿的用工量设为数组P。

//简单01背包问题
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
int n,w,g[maxn],p[maxn],ans[maxn];
int main() {
	cin>>n>>w;
	for(int i=0; i<n; i++)
		cin>>g[i]>>p[i];
	for(int i=0; i<n; i++) {//枚举物品数量
		for(int j=w; j>=p[i]; j--)//枚举背包容量,01背包,循环方向应该为从max到当前物品cost
			ans[j]=max(ans[j],ans[j-p[i]]+g[i]);
	}
	cout<<ans[w]<<endl;
	for(int i=n-1; i>=0; i--) { //枚举物品数量,前面是顺序,这里就逆序
		if(ans[w-p[i]]+g[i]==ans[w]) { //相等说明这个背包有这个物品
			printf("第%d座金矿被选中,用工量为%d,黄金量为%d\n",i+1,p[i],g[i]);
			w-=p[i];
		}
	}
	return 0;
}

课程实验3 贪心算法

实例1 最优装载问题

问题描述:有一批集装箱要装上一艘载重量为C的轮船。其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。
问题可以描述为:
在这里插入图片描述

其中,变量xi=0表示不装入集装箱i,xi=1表示装入集装箱i。

问题:采用贪心算法实现最优装载。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
int c,n;
typedef struct container container;
struct container {
	int w,index;
	bool x;
	bool operator < (const container &a) {
		return this->w<a.w;
	}
};
container b[maxn];
int main() {
	cin>>c>>n;
	for(int i=0; i<n; i++) {
		cin>>b[i].w;
		b[i].index=i+1;
	}
	sort(b,b+n);
//	for(int i=0;i<n;i++){
//		cout<<b[i].w<<" "<<b[i].index<<" "<<b[i].x<<endl;
//	}
	for(int i=0; i<n; i++) {
		if(c>=b[i].w) {
			printf("第%d个集装箱被装入货轮,重量为%d\n",b[i].index,b[i].w);
			c-=b[i].w;
		} else
			break;
	}
	return 0;
}

实例2单源最短路径问题

问题描述:给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其它各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。

问题:实现Dijkstra算法是解单源最短路径问题的贪心算法。
我们用一个例子来具体说明迪杰斯特拉算法的流程。
在这里插入图片描述

定义源点为0,dist[i]为源点0到顶点i的最短路径。其过程描述如下:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+7;
int m[maxn][maxn],dis[maxn];//m[i][j]表示点i到点j的距离,dis[i]表示点i到点0的距离
int n,k;//n个点,k条边
void dij(int begin);
int main() {
	cin>>n>>k;
	memset(m,127,sizeof(m));
	for(int i=1; i<=n; i++)
		m[i][i]=0;
	for(int i=0; i<k; i++) {
		int dist,begin,end;
		cin>>begin>>end>>dist;
		m[begin][end]=dist;
//		若为无向图
//		m[end][begin]=dist;
	}
	dij(1);
	for(int i=1; i<=n; i++)
		printf("点%d到起始点1的距离为:%d\n",i,dis[i]);
	return 0;
}
void dij(int begin) {
	bool is[maxn];
	memset(is,0,sizeof(is));
	memset(dis,127,sizeof(dis));
	for(int i=1; i<=n; i++)
		dis[i]=min(dis[i],m[begin][i]);
	is[begin]=true;
	for(int i=0; i<n-1; i++) {
		int mind=1e9+7;
		for(int k=0; k<n; k++) {
			if(!is[k]&&mind>dis[k]) {
				mind=dis[k];
				begin=k;
			}
		}
		is[begin]=true;
		for(int k=1; k<=n; k++)
			dis[k]=min(dis[k],dis[begin]+m[begin][k]);
	}
}
/*
5 7
1 2 10
1 5 100
1 4 30
2 3 50
3 5 10
4 5 60
4 3 20
*/

实例3最小生成树

问题描述:设G =(V,E)是无向连通带权图,即一个网络。E中每条边(v,w)的权为c[v][w]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树上各边权的总和称为该生成树的耗费。在G的所有生成树中,耗费最小的生成树称为G的最小生成树。
问题:用贪心算法设计策略可以设计出构造最小生成树的有效算法。采用Prim算法和Kruskal算法分别进行实现。
Prim:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+7;
const int inf=0x3f3f3f3f;
int n,k,ans;//n个点,k条边,最小生成树长度为ans
int m[maxn][maxn],dis[maxn];//m[i][j]表示点i到点j的距离,dis[i]为点i到起始点的距离
int prim();
int main() {
	cin>>n>>k;
	for(int i=1; i<=n; i++) {
		for(int k=1; k<=n; k++) {
			if(i!=k)
				m[i][k]=inf;
			else
				m[i][k]=0;
		}
	}
	for(int i=0; i<k; i++) {
		int begin,end,dist;
		cin>>begin>>end>>dist;
		m[begin][end]=m[end][begin]=min(m[begin][end],dist);
	}
	ans=prim();
	cout<<ans<<endl;
	return 0;
}
int prim() {
	bool is[maxn];
	memset(is,false,sizeof(is));
	memset(dis,0x3f,sizeof(dis));
	for(int i=1; i<=n; i++)
		dis[i]=m[1][i];
	for(int i=0; i<n; i++) {
		int u=-1;
		for(int k=1; k<=n; k++) {
			if(!is[k]&&(u==-1||dis[u]>dis[k]))
				u=k;
		}
		if(u==-1)
			break;
		ans+=dis[u];
		is[u]=true;
		for(int k=1; k<=n; k++) {
			if(!is[k])
				dis[k]=min(dis[k],m[u][k]);
		}
	}
	return ans;
}
/*
9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8
*/

Kruskal:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
struct node {
	int from,to,dis;
	bool operator < (const node &a) {
		return this->dis<a.dis;
	}
};
struct node edge[maxn];
int n,m,ans,tot;
int pre[maxn];
int find(int x);
void join(int x,int y);
void init(int x);
int main() {
	cin>>n>>m;
	init(n);
	for(int i=0; i<m; i++) {
		cin>>edge[i].from>>edge[i].to>>edge[i].dis;
	}
	sort(edge,edge+m);
	for(int i=0; i<m; i++) {
		if(find(edge[i].from)!=find(edge[i].to)) {
			join(edge[i].from,edge[i].to);
			ans+=edge[i].dis;
			tot++;
		}
		if(tot==n-1)
			break;
	}
	cout<<ans<<endl;
}
int find(int x) {
	while(x!=pre[x])
		x=pre[x];
	return x;
}
void join(int x,int y) {
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
		pre[fx]=fy;
}
void init(int x) {
	for(int i=1; i<=x; i++)
		pre[i]=i;
}
/*
9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8
*/

课程实验4 回溯法

实例1 采用回溯算法求解0-1背包问题。

问题描述:给定n种物品和一背包,物品i的重量是wi,其价值是pi,背包的容量是M,问如何选择装入背包中的物品总价值最大?

例如:有n=4件物品要装入背包,物品重量和价值如下:
在这里插入图片描述

背包容量限制是M=6,问如何选择物品,使得不超重的情况下装入背包的物品价值达到最大?

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
void dfs(int x);
bool bound(int x);
struct node {
	int w,p,index;
	double pw;
	bool operator < (const node &a) {
		return this->pw>a.pw;
	}
};
struct node bag[maxn];
int n,m,ans;
int np,nw;
bool is[maxn],book[maxn];
int main() {
	cin>>n>>m;
	for(int i=1; i<=n; i++) {
		cin>>bag[i].w>>bag[i].p;
		bag[i].pw=(double)bag[i].p/(double)bag[i].w;
		bag[i].index=i;
	}
	sort(bag+1,bag+1+n);
//	for(int i=1;i<=n;i++){
//		cout<<bag[i].index<<" "<<bag[i].p<<" "<<bag[i].w<<" "<<bag[i].pw<<endl;
//	}
	ans=-1;
	dfs(1);
	cout<<"背包总价值为:"<<ans<<endl;
	for(int i=1; i<=n; i++) {
		if(book[i]) {
			printf("第%d件物品被放入背包,重量为%d,价值为%d\n",bag[i].index,bag[i].w,bag[i].p);
		}
	}
	return 0;
}
void dfs(int x) {
	if(x>n) {
		if(np>ans) {
			ans=np;
			for(int i=1; i<=n; i++) {
				book[i]=is[i];
			}
		}
		return ;
	}//递归到边界,如果当前值大于最大值,则最优解改变 
	
	//物品有两种选择,放入背包与不放入背包
	//放入背包的要求就是当前背包容量大于物品容量 
	if(m-nw>=bag[x].w) {
		np+=bag[x].p;
		nw+=bag[x].w;
		is[x]=true;
		dfs(x+1);
		is[x]=false;
		nw-=bag[x].w;
		np-=bag[x].p;
	} 
	//不放入背包的情况可以剪枝
	//如果当前物品不放且后面的最贪心方案的背包价值无法超过目前最大值则没必要继续深搜 
	//不进行剪枝的话,算法的复杂度为2的n次方,复杂度过大 
	if(bound(x+1))
		dfs(x+1);
	return ;
}
bool bound(int x) {
	int leftw=m-nw;
	int tnp=np;
	while(x<=n&&leftw>=bag[x].w) {
		tnp+=bag[x].p;
		leftw-=bag[x].w;
		x++;
	}
	if(x<=n)
        tnp+=bag[x].pw*leftw;
    //这一步非常重要,计算了剩余背包的最大价值,否则一些测试数据会错
	if(tnp>ans)
		return true;
	else return false;
}
/*
8 200
79 83
58 14
86 54
11 79
28 72
62 52
15 48
68 62
*/

实例2 采用回溯算法求解最优装载问题

问题描述:有一批共有 n 个集装箱要装上两艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 w[i], 且重量之和小于(c1 + c2)。装载问题要求确定是否存在一个合理的装载方案可将这 n 个集装箱装上这两艘轮船。如果有,找出一种装载方案。
例如:当n=3,c1=c2=50,且w=[10,40,40]时,求解装载方案。

和上一题没什么差别,每个集装箱的选择就是装c1或者装c2。 题目只要求有一种方案即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
int n,c1,c2;
int nw;
struct node {
	int w,index;
	bool operator < (const node &a) {
		return this->w < a.w;
	}
};
struct node container[maxn];
bool book[maxn],flag;
void dfs(int x);
int main() {
	cin>>n>>c1>>c2;
	for(int i=1; i<=n; i++) {
		cin>>container[i].w;
		container[i].index=i;
	}
	sort(container+1,container+1+n);
	dfs(1);
	if(!flag)
		cout<<"无法装载"<<endl;
	return 0;
}
void dfs(int x) {
	if(flag) return ;
	if(x>n) {
		int tc1,tc2;
		tc1=c1;
		tc2=c2;
		for(int i=1; i<=n; i++) {
			if(book[i]) tc1-=container[i].w;
			else tc2-=container[i].w;
		}
		if(tc1<0||tc2<0) return;
		else {
			flag=true;
			for(int i=1; i<=n; i++) {
				if(book[i])
					printf("第%d个集装箱装入船c1,重量为%d\n",container[i].index,container[i].w);
				else
					printf("第%d个集装箱装入船c2,重量为%d\n",container[i].index,container[i].w);
			}
		}
	}
	if(c1-nw>=container[x].w) {
		nw+=container[x].w;
		book[x]=true;
		dfs(x+1);
		book[x]=false;
		nw-=container[x].w;
	}
	dfs(x+1);
}

实例3 采用回溯算法求解幂集问题

问题描述:求含N个元素的集合的幂集。
例如,对于集合A={1,2,3},则A的幂集为 p(A)={{1,2,3},{1,2},{1,3},{1},{2,3},{2},{3},Φ}。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
int n,num[maxn];
bool book[maxn];
void dfs(int x);
int main() {
	cin>>n;
	for(int i=1; i<=n; i++)
		num[i]=i;
	cout<<"{";
	dfs(1);
	cout<<"}";
	return 0;
}
void dfs(int x) {
	if(x>n) {
		int cnt=0;
		cout<<"{";
		for(int i=1; i<=n; i++) {
			if(book[i]) {
				if(cnt>0)
					cout<<",";
				cout<<i;
				cnt++;
			}
		}
		cout<<"}";
		return ;
	}
	book[x]=true;
	dfs(x+1);
	book[x]=false;
	
	dfs(x+1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值