数据结构-第四次上机实验-解题报告

7-1 连通分量 (100 分)

题目

无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。

输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.

第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.

输出格式:
1行,1个整数,表示所求连通分量的数目。

输入样例:
在这里给出一组输入。例如:

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

输出样例:
在这里给出相应的输出。例如:

2

思路

vector建图

首先我们用vector容器支持邻接表的方式实现图的存储。
并用一个vis数组对是否访问过,进行限制。

typedef struct node{
	int u;
	int v;
}edge;
vector<edge> graph[100001];
int vis[100001];

dfs深搜

对图进行深度优先搜索遍历一遍,对一个连通分支全都打上标记。
故只需要多次调用该dfs函数,便能遍历全部的连通分支。

void dfs(int u){
	vis[u]=1;
	int v;
	for(int i=0;i<graph[u].size();i++){
		v=graph[u][i].v;
		if(!vis[v]){
			dfs(v);
		}
	}
}

参考代码

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

typedef struct node{
	int u;
	int v;
}edge;
vector<edge> graph[100001];
int vis[100001];
int count=0;//记录连通分支数

void dfs(int u){
	vis[u]=1;
	int v;
	for(int i=0;i<graph[u].size();i++){
		v=graph[u][i].v;
		if(!vis[v]){
			dfs(v);
		}
	}
}
int main()
{
	
	int n,m;
	int u,v;
	edge e;
	for(int i=1;i<=n;i++) vis[i]=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		
		scanf("%d%d",&u,&v);
		e.u=u;e.v=v;
		graph[u].push_back(e);
		e.u=v;e.v=u;
		graph[v].push_back(e);
	}
	
	for(int i=1;i<=n;i++){//多次调用,遍历全部连通分支
	    if(!vis[i]){
	       dfs(i);
	       count++;
        }
	}
	printf("%d",count);
	 
} 

7-2 整数拆分 (100 分)

题目

整数拆分是一个古老又有趣的问题。
请给出将正整数 n 拆分成 k 个正整数的所有不重复方案。
例如,将 5 拆分成 2 个正整数的不重复方案,有如下2组:(1,4)和(2,3)。
注意(1,4) 和(4,1)被视为同一方案。
每种方案按递增序输出,所有方案按方案递增序输出。

输入格式:
1行,2个整数n和k,用空格分隔, 1≤k≤n≤50.

输出格式:
若干行,每行一个拆分方案,方案中的数用空格分隔。

最后一行,给出不同拆分方案的总数。

输入样例:
在这里给出一组输入。例如:

5 2

输出样例:
在这里给出相应的输出。例如:

1 4
2 3
2

思路

将n拆分为k个数,可以转化为子问题:一个数i,和,将n-i拆分为k-1个数。
故想到用递归与dfs实现。
其次,由于只要增序,故需要设置限制for(int i=max;i<=curn/2;i++)再进行深度搜索。
下面给出dfs实现回溯法的框架

回溯法的框架

(在这里附上一张老师的ppt,已备复习使用)
回溯法框架

回溯法的设计要素

 定义状态:
描述问题求解过程中每一步的状况(可行解空间);
 边界条件:
在什么情况下不再递归下去(目标状态,找到解);
 状态扩展:
可扩展状态集合(构造解)
 约束条件:
扩展出的状态应满足什么条件(剪枝);

参考代码

使用回溯法,对当前的n和当前的k进行不断的扩展和回溯
(详情见注释)

#include <iostream>
using namespace std;
int count=0;//记录方案数 
int n,k;
int a[101];
void dfs(int curn,int curk,int max)//当前的n和k 
{
	if(curk==k)//目标状态 
	{
		count++;
		a[curk]=curn;
		for(int i=1;i<k;i++)
		cout<<a[i]<<" ";
		cout<<a[k]<<endl; 
	}
	else for(int i=max;i<=curn/2;i++)//左右对称的结果,只输出前一半 
	{
		a[curk]=i;
		dfs(curn-i,curk+1,i);
	}
	
}
int main()
{
	cin>>n>>k;
	dfs(n,1,1);//从curk=1开始,达到k个,max从1开始枚举 
	cout<<count<<endl;
}

7-3 数字变换 (100 分)

题目

利用变换规则,一个数可以变换成另一个数。变换规则如下:
(1)x 变为x+1;(2)x 变为2x;(3)x 变为 x-1。
给定两个数x 和 y,至少经过几步变换能让 x 变换成 y.

输入格式:
1行,2个整数x和y,用空格分隔, 1≤x,y≤100000.

输出格式:
第1行,1个整数s,表示变换的最小步数。

第2行,s个数,用空格分隔,表示最少变换时每步变换的结果。
规则使用优先级顺序: (1),(2),(3)。

输入样例:
在这里给出一组输入。例如:

2 14

输出样例:
在这里给出相应的输出。例如:

4
3 6 7 14

思路

思路与上题略有不同,这题使用广度优先遍历bfs,并且每层遍历都得明确三个操作的优先级。(只需要if的顺序写对即可)
由于map可以用来存储不重复的pair对组,我使用逆序的对组来实现存储各个数据之间的关系。
例如(x+1,x)(2x,x)(x-1,x)。
故输出时,使用迭代器逆向遍历:

	for(vector<int>::iterator it=v.end()-1;it!=v.begin()-1;it--)
	cout<<*it<<" ";

同时bfs还需要使用queue来辅助每一层的遍历。
故需要用这三种结构进行实现

queue<int> q;
map<int,int>M;
vector<int>v;

广度优先遍历BFS

void bfs(int n,int m)
{
	q.push(n);
	while(q.front()!=m)
	{
		int x=q.front();
		q.pop();
		
		if(x+1<INF&&x<m)
		if(M.find(x+1)==M.end()) //若key值已存在,不存 
		{
			M.insert(make_pair(x+1,x));
			q.push(x+1);
		}
		
		if(x*2<INF&&x<m)
		if(M.find(x*2)==M.end()) 
		{
			M.insert(make_pair(x*2,x));
			q.push(x*2);
		}
		
		if(x-1<INF&&x-1>0)
		if(M.find(x-1)==M.end()) 
		{
			M.insert(make_pair(x-1,x));
			q.push(x-1);
		}
		//cout<<q.front()<<" ";
	}
}

参考代码

#include <bits/stdc++.h>
using namespace std;
#define INF 100001
queue<int> q;
map<int,int>M;
vector<int>v;
void bfs(int n,int m)
{
	q.push(n);
	while(q.front()!=m)
	{
		int x=q.front();
		q.pop();
		
		if(x+1<INF&&x<m)
		if(M.find(x+1)==M.end()) //若key值已存在,不存 
		{
			M.insert(make_pair(x+1,x));
			q.push(x+1);
		}
		
		if(x*2<INF&&x<m)
		if(M.find(x*2)==M.end()) 
		{
			M.insert(make_pair(x*2,x));
			q.push(x*2);
		}
		
		if(x-1<INF&&x-1>0)
		if(M.find(x-1)==M.end()) 
		{
			M.insert(make_pair(x-1,x));
			q.push(x-1);
		}
		//cout<<q.front()<<" ";
	}
}
int main()
{
	int n,m;
	int count=1;

	cin>>n>>m;
	if (n == m){//特殊情况判断  10分
		cout<<"0";
		return 0;
	} 

	bfs(n,m);
	for(map<int,int>::iterator it=M.find(m);it->second!=n;)
	{
		v.push_back(it->second);
		count++;
		it=M.find(it->second);
		//cout<<endl<<a[count-1]<< " ";
	}

	cout<<count<<endl;
	for(vector<int>::iterator it=v.end()-1;it!=v.begin()-1;it--)
	cout<<*it<<" ";
	cout<<m<<endl;
	//map<int,int>::iterator it=M.find(6);
	//cout<<endl<<it->second;

}

7-4 旅行 I (100 分)

题目

五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。
将所有城市编号为1到n,你出发的城市编号是s。你想知道,到其它城市的最小费用分别是多少。
如果可能,你想途中多旅行一些城市,在最小费用情况下,到各个城市的途中最多能经过多少城市。

输入格式:
第1行,3个整数n、m、s,用空格分隔,分别表示城市数、交通方式总数、出发城市编号, 1≤s≤n≤10000, 1≤m≤100000 。

第2到m+1行,每行三个整数u、v和w,用空格分隔,表示城市u和城市v的一种双向交通方式费用为w , 1≤w≤10000。

输出格式:
第1行,若干个整数Pi,用空格分隔,Pi表示s能到达的城市i的最小费用,1≤i≤n,按城市号递增顺序。

第2行,若干个整数Ci,Ci表示在最小费用情况下,s到城市i的最多经过的城市数,1≤i≤n,按城市号递增顺序。

输入样例:
在这里给出一组输入。例如:

5 5 1
1 2 2
1 4 5
2 3 4
3 5 7
4 5 8

输出样例:
在这里给出相应的输出。例如:

0 2 6 5 13
0 1 2 1 3

思路

单源最短路问题
延续第一题的思路,用vector建图
唯一要注意的,应该是最多经过的城市数。不然只能过3个点(否则连案例的的输出都将会是01212)不说也知道肯定是在写题目时疯狂出错/doge

vector建图

typedef struct node1{
	int end;//终点
	int cost;
}edge;

vector<edge>graph[100001];
int dis[100001];//距离_数组
bool vis[100001];//是否访问_数组
int path[100001];//记录该点 前驱点

Dijkstra算法

迪杰斯特拉算法,套用即可。可以稍微注意需要从i=1开始遍历。
同时,在算法中维护一个int count_p[100001];数组,来存储每条路径的长度,并且遇到更长路径,则更新之。

void Dijkstra(int s, int e)//start  end
{
	for (int i = 1; i <=P_num; i++)//初始化
	{
		dis[i] = INF;
		vis[i] = false;
		path[i] = -1;
	}
	int len;
	dis[s] = 0;
	int pos, min;
	for (int j = 1; j <=P_num; j++)
	{
		min = INF;
		for (int i = 1; i <=P_num; i++)
		{
			if (vis[i] == 0 && dis[i] < min)
			{
				min = dis[i]; pos = i;
			}
		}
		vis[pos] = true;

		//更新此时position的dis
		len = graph[pos].size();
		for (int k = 0; k <len; k++)
		{
			
			if (vis[graph[pos][k].end] == 0 && dis[graph[pos][k].end] >graph[pos][k].cost + dis[pos])
			{
				dis[graph[pos][k].end] = graph[pos][k].cost + dis[pos];//松弛
				//path[graph[pos][k].end] = pos;//path记录前驱,用于递归逆序输出
				count_p[graph[pos][k].end]=count_p[pos]+1; 

			}
			else if(vis[graph[pos][k].end] == 0 && dis[graph[pos][k].end]==graph[pos][k].cost + dis[pos])
			{
				if(count_p[graph[pos][k].end]<count_p[pos]+1)
				count_p[graph[pos][k].end]=count_p[pos]+1; 
			}
		}
		
	}
	//cout << "从起点"<<s<<"到终点"<<e<<"的距离为"<<dis[e]<<endl;
	//for(int i=1;i<=100;i++) 
}

参考代码

#include<vector>
#include<algorithm>
#include<iostream>
#define INF 1000000;//最大值

using namespace std;


typedef struct node1{
	int end;//终点
	int cost;
}edge;

vector<edge>graph[100001];
int dis[100001];//距离_数组
bool vis[100001];//是否访问_数组
int path[100001];//记录该点 前驱点
int count_p[100001];
int c_p=0;
int P_num, E_num; // 点数、边数
int start;
inline void Build(int x, int y, int cost)//设置每个点
{
	edge e;
	e.end = y; e.cost = cost;
	graph[x].push_back(e);
}

inline void input()//输入图
{
	int a, b, w;
	edge e;
	//cout << "请输入点数、边数:";
	cin >> P_num >> E_num>>start;
	//cout << "请输入"<<E_num<<"组起点、终点、权值:"<<endl;
	for (int i = 1; i <= E_num; i++)
	{
		cin >> a >> b >> w; 
		Build(a, b, w);
		Build(b, a, w); //无向
	}
}

void Dijkstra(int s, int e)//start  end
{
	for (int i = 1; i <=P_num; i++)//初始化
	{
		dis[i] = INF;
		vis[i] = false;
		path[i] = -1;
	}
	int len;
	dis[s] = 0;
	int pos, min;
	for (int j = 1; j <=P_num; j++)
	{
		min = INF;
		for (int i = 1; i <=P_num; i++)
		{
			if (vis[i] == 0 && dis[i] < min)
			{
				min = dis[i]; pos = i;
			}
		}
		vis[pos] = true;

		//更新此时position的dis
		len = graph[pos].size();
		for (int k = 0; k <len; k++)
		{
			
			if (vis[graph[pos][k].end] == 0 && dis[graph[pos][k].end] >graph[pos][k].cost + dis[pos])
			{
				dis[graph[pos][k].end] = graph[pos][k].cost + dis[pos];//松弛
				//path[graph[pos][k].end] = pos;//path记录前驱,用于递归逆序输出
				count_p[graph[pos][k].end]=count_p[pos]+1; 

			}
			else if(vis[graph[pos][k].end] == 0 && dis[graph[pos][k].end]==graph[pos][k].cost + dis[pos])
			{
				if(count_p[graph[pos][k].end]<count_p[pos]+1)
				count_p[graph[pos][k].end]=count_p[pos]+1; 
			}
		}
		
	}
	//cout << "从起点"<<s<<"到终点"<<e<<"的距离为"<<dis[e]<<endl;
	//for(int i=1;i<=100;i++) 
}

int main()
{
	std::ios::sync_with_stdio(false);
	input();
	//for(int i=0;i<=P_num;i++) count_p[i]=0;
	Dijkstra(start,1);
	for(int i=1;i<=P_num;i++) 
	{
		
		if(i==P_num)cout<<dis[i];
		else cout<<dis[i]<<" ";
		c_p++;
	}
	cout<<endl;
	for(int i=1;i<=P_num;i++)
	{
		if(i==P_num)cout<<count_p[i];
		else cout<<count_p[i]<<" ";
	}
	
	cout<<endl;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值