数据结构-第四次上机实验-解题报告
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;
}