数据结构第四次上机
一、7-1 连通分量 (100 分)
输入格式:
第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
思路:
1. 存储问题: 存储图的结构有连接链表与连接矩阵,本题选择连接矩阵存储图。特别的,本题需要存储的是无向图,而连接链表存储的边是又向的,故两点之间的边需要存为双向路。
cin >> m >> n;
//从m到n
edge* q = new edge;
......
g->verlist[m-1].next=q;
//从n到m
edge* q = new edge;
......
g->verlist[n-1].next=q;
2.遍历联通分支的方法: 图的遍历算法每次都只能遍历一个联通分量,DFS与BFS在这里使用任意一种都可以达到目的,并且使用DFS的递归算法在本题中也不会超时。 调用的次数即为连通分支的个数。
具体代码:
1.存图:
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
//存储图的结构
typedef struct edge {
int ii;
int w;
struct edge* next;
} edge;
typedef struct vertex {
int ii;
struct edge* next;
}vertex;
typedef struct graph {
int vv, ee;
vector<vertex> verlist;
}graph;
graph* g = new graph;
int main()
{
scanf("%d%d",&g->vv,&g->ee);
for (int i = 1; i <= g->vv; i++)
{
vertex* p = new vertex;
p->ii = i ;
p->next = NULL;
g->verlist.push_back(*p);
}
for (int i = 0; i < g->ee; i++)
{
int m, n;
cin >> m >> n;
//存储从m到n的有向边
edge* q = new edge;
q->ii = n;
q->w = 0;//无权边
q->next = NULL;
if (!g->verlist[m-1].next)//如果遍链表为空
g->verlist[m-1].next = q;
else//非空则找到最后的位置
{
edge* head = g->verlist[m-1].next;
while (head->next)
head = head->next;
head->next = q;
}
//存储从n到m的有向边
q = new edge;
q->ii = m;
q->w= 0;
q->next = NULL;
if (!g->verlist[n-1].next)
g->verlist[n-1].next = q;
else
{
edge* head = g->verlist[n-1].next;
while (head->next)
head = head->next;
head->next = q;
}
}
}
2.(1)DFS遍历
int vis[50005];
void DFS(int v)
{
vis[v] = 1;
for (edge* pp = g->verlist[v - 1].next; pp; pp = pp->next)
{
if (!vis[pp->ii])
{
DFS(pp->ii);
}
}
}
int main()
{
//存图
int n=0;
for(int i=0;i<g->vv;i++)
{
if(vis[i+1]==0)
{
vis[i+1]=1;
DFS(i+1);
n++;
}
}
printf("%d",n);
}
(2)BFS遍历
vis[50005]
queue<int>qq;
int main()
{
//存图
int n=0;
for(int i=0;i<g->vv;i++)
{
if(vis[i+1]==0)
{
vis[i+1]=1;
qq.push(i+1);
while (!qq.empty())
{
int v = qq.front();
qq.pop();
edge* p = g->verlist[v - 1].next;
for (p; p; p = p->next)
{
if (vis[p->ii] == 0)
{
vis[p->ii] = 1;
qq.push(p->ii);
}
}
}
n++;
}
}
printf("%d",n)
}
二、整数拆分 (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
思路:
1. 将一个数分解为多个数的和是一个典型的使用递归方法解决的问题。链接为我之前总结的思路与方法,本题在数据结构中使用高级语言设计同样的方法是不会超时的。
链接: [递归例题总结]–正整数n,按第一项递减的顺序依次输出其和等于n的所有不增的正整数和] https://blog.csdn.net/weixin_51376942/article/details/112204620?spm=1001.2014.3001.5501.
具体代码
#include <stdio.h>
#include <iostream>
using namespace std;
int n,k,num;
int d[100];
void divid(int nn,int kk)
{
if(kk==k)
{
d[k]=nn;
num++;
for(int i=1;i<=kk;i++)
{
printf("%d",d[i]);
if(i==kk)printf("\n");
else printf(" ");
}
return ;
}
if(nn==0&&kk!=k) return ;
for(int i=d[kk-1];i<=nn/(k-kk+1);i++)//保证分解的数递增
{
d[kk]=i;
divid(nn-i,kk+1);
}
}
int main()
{
scanf("%d%d",&n,&k);
d[0]=1;
divid(n,1);
printf("%d",num);
return 0;
}
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
思路
1. 从初始的x使用三种变换规则得到的数,再次依次使用三种变换规则直到第一次得到目标数字,整个计算的过程中实际形成了一个图,而计算的过程实际上是一次对图的BFS遍历,所以,本题实际上是在一张潜在的图上找到从x到y的最短路,并输出这条路上的记录的所有值。
2. 引入一个记忆数组,对于已经入队的数字,不再重复入队,否则图将扩展为一个三叉树,多次的重复计算在本题中会导致第三个内存超限(即使内存足够,也会计算超时),第二个答案错误。
3. 引入一个存路径的数组和一个存储计算所得的全部值的数组,递归从头到位输出路径上的值。
注意:
全局数据的下表范围:标记数组v[] 下标的最大值为计算过程中所得的最大值,本题2000010即可。路径数组path[] 的下标的最大值为计算过程中所得的所有值,本题中开到1e5+5即可。
具体代码:
#include <stdio.h>
#include <queue>
using namespace std;
typedef struct point{
int x;
int i;
point(int n=0,int m=0):x(n),i(m){}
} poi;
queue <poi>q;
int a[100005]/*存储计算所得所有值*/,path[100005]/*存储路径*/,v[200010]/*标记数组*/,step;
void print(int i)
{
if(path[i]!=-1)
{
step++;
print(path[i]);
}
else
{
printf("%d\n",step);
return ;
}
printf("%d",a[i]);
if(step--!=1) printf(" ");
}
int main()
{
int x,y;
scanf("%d%d",&x,&y);
int i=0;
path[i]=-1;
a[i]=x;
if(x==y)
{
printf("0\n");
return 0;}
q.push(poi(x,i++));
while(1)
{
poi xx=q.front();
q.pop();
if(!v[xx.x+1])
{
q.push(poi(xx.x+1,i));
v[xx.x+1]=1;
a[i]=xx.x+1;
path[i++]=xx.i;
if(xx.x+1==y) break;
}
if(!v[xx.x*2]&&xx.x*2<2*y)
{
q.push(poi(xx.x*2,i));
v[xx.x*2]=1;
a[i]=xx.x*2;
path[i++]=xx.i;
if(xx.x*2==y) break;
}
if(!v[xx.x-1]&&xx.x>0)
{
q.push(poi(xx.x-1,i));
v[xx.x-1]=1;
a[i]=xx.x-1;
path[i++]=xx.i;
if(xx.x-1==y) break;
}
}
print(i-1);
}
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
思路:
1. 本题实质上是求单元最短路问题,可用的算法有1.Dijkstra(无法处理负权边)2.Bellman—ford算法(可以处理负权边)3.SPFA算法(不适合负回路)。本题具有实际意义,所以不会出现负权边,故Dijkstra算法是可行的。但介于基础的Dijistra算法的时间效率低(虽然在本题中并不会超时),需要使用堆优化。
2. 由于本题要求输出最短且路由点更多的路,所以无需存储路径的path数组,而需要一个记录个点到源点路径上的点的个数。故在松弛步骤时,当到达某点的旧路代价与新路代价相同时,若新路路由的点更多重新赋值该点路由点的值。
注意:
普通dijikstra算法与堆优化的Dijikstra算法的循环条件是不同的,沿用基础Dijistra算法的循环条件会导致生成错误的单元最短路。在本题中会使用例三与用例四答案错误。
具体代码:
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#define MAX 1e8;
using namespace std;
typedef struct edge {
int ii;
int cost;
struct edge* next;
} edge;
typedef struct vertex {
int ii;
struct edge* next;
}vertex;
typedef struct graph {
int vv, ee;
vector<vertex> verlist;
}graph;
graph* g = new graph;
typedef struct qbase {
int key, value;
}qbase;
struct cmp {
bool operator()(const qbase a, const qbase b)
{
return a.value > b.value;
}
};
priority_queue<qbase, vector<qbase>, cmp> qq;//使用优先对列进行队优化
int path[10005], ll[10005],s;
long long dis[10005];
int DIJ()
{
for (int i = 0; i < 10005; i++)
{
dis[i] = MAX;
ll[i] = 0;
}
dis[s] = 0;
qbase aaa;
aaa.key = s;
aaa.value = 0;
qq.push(aaa);
while(!qq.empty())
{
long long min = qq.top().value;
int kk = qq.top().key;
qq.pop();
edge* p = g->verlist[kk - 1].next;
for (p; p; p = p->next)
{
if (dis[p->ii] > dis[kk] + p->cost)
{
ll[p->ii] = ll[kk] + 1;
dis[p->ii] = dis[kk] + p->cost;
//松弛后的点入队
qbase tmp;
tmp.key = p->ii;
tmp.value = dis[p->ii];
qq.push(tmp);
}
else if (dis[p->ii] == dis[kk] + p->cost)
{
if (ll[p->ii] < ll[kk] + 1)
ll[p->ii] = ll[kk] + 1;
}
}
}
return 0;
}
int main()
{
scanf("%d%d%d", &g->vv, &g->ee, &s);
for (int i = 1; i <= g->vv; i++)
{
vertex* p = new vertex;
p->ii = i;
p->next = NULL;
g->verlist.push_back(*p);
}
for (int i = 0; i < g->ee; i++)
{
int m, n, c;
cin >> m >> n >> c;
edge* q = new edge;
q->ii = n;
q->cost = c;
q->next = NULL;
if (!g->verlist[m - 1].next)
{
g->verlist[m - 1].next = q;
}
else
{
edge* head = g->verlist[m - 1].next;
while (head->next)
{
head = head->next;
}
head->next = q;
}
q = new edge;
q->ii = m;
q->cost = c;
q->next = NULL;
if (!g->verlist[n - 1].next)
{
g->verlist[n - 1].next = q;
}
else
{
edge* head = g->verlist[n - 1].next;
while (head->next)
{
head = head->next;
}
head->next = q;
}
}//建图
DIJ();
for (int i = 0; i < g->vv; i++)
{
printf("%lld", dis[i + 1]);
if (i != g->vv - 1) printf(" ");
}
printf("\n");
for (int i = 1; i <= g->vv; i++)
{
printf("%d", ll[i]);
if (i != g->vv)printf(" ");
}
}