《算法分析与设计》实验一到六

实验一 归并排序求逆序对

原理:归并排序是采用分治的方法,均摊了每一段的时间复杂度
时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( n ) O(n) O(n)
原理在装进新数组的时候记录逆序对的个数
比如当 a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j] 分界点为 m i d mid mid 那么这一段的逆序对数量
m i d − i + 1 mid-i+1 midi+1
结果:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+100;
long long ans=0;// 记录逆序对 
long long a[N];
long long b[N];//相当于桶 
int n;

struct node{  //记录逆序对用 
	int l,r;
	bool operator <(const node &A){
		return l<A.l||(l==A.l&&r<A.r);
	}
}A[N],B[N];
int atot,btot; 

//归并排序求逆序对 
void mergesort(int l,int r){
	if(r-l<1) return;
	int mid=(l+r)>>1; //砍成两半 
	
	mergesort(l,mid); //分治 
	mergesort(mid+1,r);
	
	int i=l,j=mid+1;
	for(int k=l;k<=r;k++){
		if(j>r||a[i]<=a[j]&&i<=mid) b[k]=a[i++];
		else {
			for(int z=i;z<=mid;z++) A[++atot]={a[z],a[j]};  
			b[k]=a[j++],ans+=mid-i+1;//记录逆序对 
		}
	}
	for(int k=l;k<=r;k++) a[k]=b[k];   
	 
}

//暴力检验,暴力求逆序对 
int cnt=0;
void bf(int n){
	int c[N];
	cnt=0;
	memcpy(c,a,sizeof(c));
	for(int i=1;i<=n-1;i++)
		for(int j=i+1;j<=n;j++)
			if(a[i]>a[j]) {
				B[++btot]={a[i],a[j]};
				cnt++;
			} 
	
} 

int main (){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];

	ans=0;
	atot=btot=0;
	bf(n); //暴力 
	mergesort(1,n);
	sort(A+1,A+1+atot);
	sort(B+1,B+1+btot); 
	
	cout<<endl<<"两边结果已经按照逆序对第一个数排好序:\n"; 
	printf("\n归并排序结果:    暴力枚举检验:   \n");
	for(int i=1;i<=atot;i++){
		printf("[%d %d]",A[i].l,A[i].r);
		cout<<"                "; 
		printf("[%d %d]\n",B[i].l,B[i].r);
	} 
	
	printf("总计:%d                %d\n",ans,cnt);
	return 0;
}

/*
test 
11
23 13 35 6 19 50 28 38 26 17 45


*/

实验二 折半查找区间

给定一个区间[a,b]。在一个升序序列中,使用减治法,设计一个改良版的折半查找算法,给出区间内的所有元素。

思路:首先序列时非严格单调递增的策略,采用分治的策略,对区间对半划分,若区间在给定范围之内直接输出结果,若不在范围之内则继续对半划分进行递归
结果:在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
int a[N];
int n;

void solve(int l,int r,int L,int R){
	if(L<=a[l]&&a[r]<=R){ //在判定范围之内直接输出并且结束递归
		for(int i=l;i<=r;i++)
			cout<<a[i]<<" ";
		return;
	}
	if(l>=r) return; // 只有一个数,直接返回
	int mid=(l+r)>>1; //取中间的值
	if(L<=a[mid])solve(l,mid,L,R); //向左递归
	if(R>a[mid]) solve(mid+1,r,L,R); //向右边递归
} 

int main(){
	cout<<"输入元素个数:";
	cin>>n;
	cout<<"输入元素:";
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int m;
	cout<<"输入操作次数:";
	cin>>m;
	for(int i=1;i<=m;i++){
		int L,R;
		cin>>L>>R;
		printf("在区间[%d %d]的数为:",L,R);
		solve(1,n,L,R);
		cout<<endl;
	}
	return 0;
}
/*
10
2 4 6 8 11 13 15 17 19 20
4
4 15
7 18
14 25
25 30
*/

实验三 货币支付(动态规划,完全背包问题)

其实就是个完全背包模型
f ( i , j ) f(i,j) f(i,j) 表示前i种货币凑出j价值的最小货币数
当第i种货币面值高于 j j j面值时,说明只能继承前一种货币的最小货币数
转移状态方程有: f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i1,j)
当第i种货币面值低于于 j j j面值时。
当前j的面值可以由 j − v [ i ] j-v[i] jv[i]的面值凑过来,或者继承第 i − 1 i-1 i1种 的货币数,他们两个取最小即可
转移状态方程 f ( i , j ) = m i n { f ( i − 1 , j ) , f ( i , j − v ( i ) ) + 1 } f(i,j)=min\{f(i-1,j),f(i,j-v(i))+1\} f(i,j)=min{f(i1,j),f(i,jv(i))+1}
在这里插入图片描述

code:

#include<bits/stdc++.h>

using namespace std;

const int N=10000;
int f[N];
//int f[100][N];
int v[100];

int path[50][N];

int main(){
	int n;
	cout<<"输入货币种类数:";
	cin>>n;
	cout<<"输入货币价值:"; 
	for(int i=1;i<=n;i++)
		cin>>v[i];
	memset(f,0x3f,sizeof(f));
	/* 
	f[0][0]=0;//0 元不用凑数,所以最少为0 
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++){
			if(j<v[i]) f[i][j]=f[i-1][j]; //当前第i种货币价值比你要的面值高 
			else{
				//  
				f[i][j]=min(f[i][j-v[i]]+1,f[i-1][j]);
			}
		}	
	
	*/
		
	//我们发现每次转化都是由前一个转化而来
	//所以可以压缩种类数,使得空间复杂度降为O(m) 
	//面值倒序凑数就行了 
	f[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=v[i];j<=1000;j++)
			if(f[j]>f[j-v[i]]+1){
				f[j]=f[j-v[i]]+1;
				path[i][j]=1; //记录路径 
			} 

	}
	
	while(1){
		printf("输入要凑的面值:"); 
		int m;
		cin>>m;
		cout<<"路径:"<<endl; 
		int a=n,b=m;
		while(a>=0&&b>=0){
			if(path[a][b]){
				printf("%d ",v[a]); //路径输出 
				b-=v[a];
			}
			else a--;
		} 
		cout<<"\n最少次数:"<<f[m]<<endl;
	}
	return 0;
}

/*
test:
4 
1 2 5 10
167
20
66


*/ 

实验四 汽车加油(贪心)

一种贪心策略就是我尽量跑远,那么加油的次数就会减少
算法描述:如果当前能跑的公里数大于加油站之间的距离
那么就不加油
否则就加油
之间扫描一下数组就可以了,时间复杂度为O(N)
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

const int N=1000;
int a[N];

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>a[i];
	int tmp=n;
	int cnt=0;
	for(int i=0;i<=m;i++){
		if(tmp>=a[i]){
			tmp-=a[i];
		}
		else{
			tmp=n;
			tmp-=a[i];
			cout<<"编号:"<<i-1<<endl; 
			cnt++;
		}
	}
	cout<<"加油次数:"<<cnt<<endl; 
	return 0;
}
/*

7 7
1 2 3 4 5 1 6 6
*/ 

实验五 作业调度(dfs)

采用深度遍历枚举作业次序,当作业全部完成则记录时间与调度次序,如果当前方案的时间高于最优方案,我们进行可行性剪枝

具体做法递归+回溯

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

code:

#include<bits/stdc++.h>

using namespace std;

const int N=1000;
int a[N];
int n,m;
int work[N][4]; //工作 
int step[N]; //调度方案 
int pos[N];  //当前位置 

int ans=(1<<30); //先将结果置于无穷大  
void dfs(int d,int tim1,int tim2,int res){  //深度,工厂1的时间  工厂2的时间 工厂3的时间(最终结果) 
	
	if(res>ans) return;//大于当前最优的结果退出,剪枝 
	
	if(d>n){
		if(res<ans){
			for(int i=1;i<=n;i++) step[i]=pos[i]; //记录调度方案 
			ans=res; //记录当前最小时间 
		} 
		return; //记得返回 
	}
		
	for(int i=d; i<=n; i++) {
        	int x=tim1,y=tim2,z=res;

            tim1+=work[pos[i]][1];
            tim2=(tim1>tim2?tim1:tim2)+work[pos[i]][2];
			res=(tim2>res?tim2:res)+work[pos[i]][3];
            //交换两个作业的位置,把选择出的原来在pos[i]位置上的任务调到当前执行的位置pos[d]
            swap(pos[d],pos[i]);  
            dfs(d+1,tim1,tim2,res);   
            swap(pos[d],pos[i]); 

            tim1=x,tim2=y,res=z;//回溯
	}
}


int main(){
	cin>>n;// 几个作业
	for(int j=1;j<=3;j++)
		for(int i=1;i<=n;i++)  cin>>work[i][j];
	for(int i=1;i<=n;i++) pos[i]=i;
	dfs(1,0,0,0); 
	cout<<"最短时间:"<<ans<<endl; 
	cout<<"作业调度方案:"; 
	for(int i=1;i<=n;i++) 
		cout<<" "<<step[i];
	cout<<endl; 
}

/* 
test:
4
3 5 2 6
4 2 5 1
2 1 2 1


4
5 3 2 6
4 2 5 1
2 1 2 1


4
5 3 2 1
4 2 5 1
2 1 2 1

*/

实验六 机器组装设计

机器组装(bfs)
采用广度搜索的思想,对零件各个部分进行搜索,当为第n+1个零件时,结束当前搜索,更新当前最优的结果,对于部分搜索,低于当前最优的结果直接剪枝。
最坏的时间复杂度为O(m^n)

在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

const int N=1000;
const int INF=0x3f3f3f3f; 
int val[N][N],weight[N][N];
struct node{
	int pos,val,weight;//位置,累积价格,累积重量 
	string ways;//记录路径 
}ans;

int n,m,d;


void bfs(){
	queue<node> q;//声明队列 
	ans={0,INF,INF,""}; //先将目标设为价格无穷大,重量无穷大 
	q.push({1,0,0});
	while(!q.empty()){
		node no=q.front();q.pop();
		int pos=no.pos,v=no.val,w=no.weight;
		if(pos==n+1){
			//如果重量更轻或者等重量价格更低则更新最优解 
			if(no.weight<ans.weight||no.weight==ans.weight&&no.val<ans.val) 
				ans=no;
			continue;
		}
		for(int i=1;i<=m;i++){	
			int nv=v+val[pos][i]; //选择这个零件后当前 
			int nw=w+weight[pos][i];
			if(nv>d||nw>ans.weight) continue; //当前超过价钱d或者重量超过当前最优解   剪枝 
			char ch=i+'0';//当前路径 
			q.push({pos+1,nv,nw,no.ways+ch});	
		}
	}
}

int main(){
	cin>>n>>m>>d;
	for(int j=1;j<=m;j++)
		for(int i=1;i<=n;i++)
			cin>>val[i][j];
	
	for(int j=1;j<=m;j++)
		for(int i=1;i<=n;i++)
			cin>>weight[i][j];
	bfs();
	cout<<"总重量"<<ans.weight<<" 总价格:"<<ans.val<<endl;
	string ways=ans.ways;
	for(int i=0;i<ways.size();i++){
		cout<<"第"<<i+1<<"零件在"<<ways[i]<<"供应商买"<<"      价格:"<<val[i+1][ways[i]-'0']<<"     重量:"<<weight[i+1][ways[i]-'0']<<endl; 
	}
	return 0;
} 

/*
测试数据 
4 3 28
9 7 5
10 8 7
5 8 9
4 7 5

3 2 1
2 1 1
1 2 2 
1 2 2

*/
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值