Data Structure第七次作业讲解

Data Structure第七次作业讲解

写给读者的话(务必阅读)

期中以来,有不少同学向我反应代码写的慢、正确率不高等问题。由于OS已经爆炸闲的没事干 因此我决定将自己原来写的代码重构重构,并整理成博客附上整个思路的讲解。首先,我必须申明,博客所写的东西,是供想提升自己代码水平,理清写码思路的同学用的。我希望同学们能够明白:作为一个考试分数占 80% 的学科,抄袭他人代码完成作业不是白赚了那 20% 的分数,而是失去了一次良好的练兵的机会(从本次开始,文章中给出的代码均已提交评测,抄袭需谨慎)。其次,个人所写代码只是提供一个写码、思考问题的思路,并不代表题目的唯一解法,更不代表最优解法没有提交到课程网站上测试,只在本地通过测试数据,甚至可能有bug噢。我希望我的代码能够起到抛砖引玉的作用,能够让同学对课上内容有更深刻的理解,写代码时能够有更多更好的想法。最后,我希望同学们在完成作业的同时,能够对自己的代码进行复杂度的分析。数据结构的使用,往往离不开对性能的约束,因此,掌握复杂度的分析也是这门课程重要的一环。

关于代码风格

本文中所有的代码风格皆采取 OO 的标准,同时作者也希望同学们能够以这种标准约束自己,这样也会方便助教 debug。简单来说,大致约束如下:

1、符号后带空格。

2、大括号不换行。

3、if、while、for 等括号两端应该带空格,并且一定要用大括号括起来。

4、一行不要写过多字符(不超过60),较长的判断可以换行处理。

5、缩进为 4 个空格,不同层次间要有合适的缩进。

6、一行只声明一个变量,只执行一个语句。

关于使用到的工具

采取了dhy大佬的意见,决定新添加这个栏目,对本次代码中使用到的基础的一些数据结构或是函数进行一些简单的讲解,便于大家的使用和理解。

1、快速读入

inline int read() { //快速读入,可以放在自己的缺省源里面 
	int x = 0; //数字位 
	int f = 1; //符号位 
	char ch = getchar(); //读入第一个字符 
	while (!isdigit(ch)) { //不是数字 
		if (ch == '-') { //特判负号 
			f = -1;
		}
		ch = getchar();
	}
	while (isdigit(ch)) { //读入连续数字 
		x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1) 
		ch = getchar();
	}
	return x * f;
}

快速读入是比较好用的一种读入的写法,我这里的实现是通过循环读入直到得到下一个数字,在具体的题目中也可以根据自己的需要对循环的条件和结束条件做更改来读入字符串等。(由于只涉及到简单循环,这里不作更深入的讲解)。切忌不经思考和理解就使用,容易出现读入死循环等问题。

2、链式前向星

在解决需要建边(如:树、图)相关的问题时,比较方便的一种数据结构。在第一道题中用到了类似的结构,因此我们在这里做个讲解。

typedef struct edge {
	int to; //指向这条边到达的对象
	int nxt; //指向当前表头的下一条边,为 0 则代表没有
} Edge;
Edge e[maxn];
int h[maxn];
int cnt;

void adde(int x, int y) { //建一条 x 向 y 的边
	e[++cnt] = (Edge) {y, h[x]}; //产生新的指向 y 的表头,并将 nxt 指向之前的表头
	h[x] = cnt; //更新表头
}

void forEach(int x) {
	int i;
	for (i = h[x]; i; i = e[i].nxt) { //遍历时从表头开始依次访问每个边,取出其指向的点
		int y = e[i].to;
		forEach(y);
	}
}

如此图所示,我们新加入编号为 3 的边

初始状态 e[1] = {2, 0} (前面为to,后面为nxt)

​ e[2] = {3, 1}

​ h[1] = 2

这时我们新建 e[3] = {4, h[1]} 即 {4,2}

再令 h[1] = 3

那么遍历的时候我们可以通过 h[1] 找到 e[3],再通过 e[3].nxt 找到 e[2], 再通过 e[2].nxt 找到 e[1],就获得了完整的边的信息。

3、并查集

一种多用在解决集合关系中的算法,实现 kruskal 所需要的工具。

简单来讲,就是维护一个 prt 数组。

当 prt[i] = i 时,代表这个点就是集合的代表元素。否则一直往上找到一个这样的点。

连接两个点 x,y 就是分别找到他们的代表元素 f1 和 f2

如果 f1 == f2 ,则代表两个点已经在同一集合中,不需要更改并查集

否则令 prt[f1] = f2 ,完成建边。

int getf(int x) { //找到最上面的父亲 
	return x == prt[x] ? x : (prt[x] = getf(prt[x]));
} 
//注意这里可以看见,将prt[x]改为了最上面的父亲,这是为了优化时间复杂度。这里就不赘述,想知道的同学可以课上问我。
void combine(int x, int y) {
	int f1 = getf(x);
 	int f2 = getf(y);
    	if (f1 == f2) {
        	return;
    	}
    	prt[f1] = f2;
}

void pre() { //注意初始化时每个点单独是一个集合
	int i;
    	for (i = 1; i <= n; ++i) {
        	prt[i] = i;
    	}
}

4、SPFA

一种最短路算法,思路简单暴力,实现简单,复杂度优秀(如果没有被数据针对的话)

整体架构大致如下:

我们用一个 vst 数组记录点在不在队列中。

如果一个点的最短路被更新时不在队列中,就把它加入队列中

本质上就是对暴力BFS的优化 是不是很简单

while (size()) { // SPFA
		int x = front();
		pop();
		vst[x] = 0;
		int i;
		for (i = h[x]; i; i = e[i].nxt) {
			int y = e[i].to;
			if (dis[y] > dis[x] + e[i].w) {
				dis[y] = dis[x] + e[i].w;
				if (!vst[y]) {
					push(y); 
					vst[y] = 1; 
				} 
			}
		}
	}

第一题:图遍历(图-基本题)

题目描述

【问题描述】

给定一个无向图和一个图顶点,编程输出该图删除给定顶点前后按深度优先遍历及广度优先遍历方式遍历的图顶点序列。

给定的无向图和图顶点满足以下要求:

1、无向图的顶点个数n大于等于3,小于等于100,输入时顶点编号用整数0~n-1表示;

2、无向图在删除给定顶点前后都是连通的;

3、无论何种遍历,都是从编号为0的顶点开始遍历,访问相邻顶点时按照编号从小到大的顺序访问;

4、删除的顶点编号不为0。

【输入形式】

先从标准输入中输入图的顶点个数和边的个数,两整数之间以一个空格分隔,然后从下一行开始分行输入每条边的信息(用边两端的顶点编号表示一条边,以一个空格分隔顶点编号,边的输入次序和每条边两端顶点编号的输入次序可以是任意的,但边不会重复输入),最后在新的一行上输入要删除的顶点编号。

【输出形式】

分行输出各遍历顶点序列,顶点编号之间以一个空格分隔。先输出删除给定顶点前的深度优先遍历顶点序列和广度优先遍历顶点序列,再输出删除给定顶点后的深度优先遍历顶点序列和广度优先遍历顶点序列。

【样例输入】

9 10

0 1

0 2

1 4

1 3

1 8

8 6

3 6

7 2

7 5

5 2

3

【样例输出】

0 1 3 6 8 4 2 5 7

0 1 2 3 4 8 5 7 6

0 1 4 8 6 2 5 7

0 1 2 4 8 5 7 6

【样例说明】

输入的无向图有9个顶点,10条边(如下图所示),要删除的顶点编号为3。

graph.jpg

从顶点0开始,按照深度优先和广度优先遍历的顶点序列分别为:0 1 3 6 8 4 2 5 7和0 1 2 3 4 8 5 7 6。删除编号为3的顶点后,按照深度优先和广度优先遍历的顶点序列分别为:0 1 4 8 6 2 5 7和0 1 2 4 8 5 7 6。

题目大意

按照题目要求从 0 开始进行 dfs 和 bfs

题目思路

这个题要求遍历按照编号的顺序,因此我们用邻接矩阵来存再枚举可以自动满足这个要求

除此之外我们需要一个vst数组来记录访问了哪些点

然后就正常进行 bfs 和 dfs 即可

代码实现

#include <stdio.h> 
#include <math.h>
#include <string.h>
#include <ctype.h>

inline int read() { //快速读入,可以放在自己的缺省源里面 
	int x = 0; //数字位 
	int f = 1; //符号位 
	char ch = getchar(); //读入第一个字符 
	while (!isdigit(ch)) { //不是数字 
		if (ch == '-') { //特判负号 
			f = -1;
		}
		ch = getchar();
	}
	while (isdigit(ch)) { //读入连续数字 
		x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1) 
		ch = getchar();
	}
	return x * f;
}

#define BIG 1000005
int q[BIG]; //为 BFS 提供队列操作的各种函数 
int head, tail;

void clear() { //清空队列 
	head = 1;
	tail = 0;
}

int front() { //取出队首 
	return q[head];
} 

void pop() { //弹出队首 
	++head;
}

void push(int x) { //放值进入队尾 
	q[++tail] = x;
}

int size() { //获得队列大小 
	return tail - head + 1;
}

#define maxn 105
int n, m;
int map[maxn][maxn]; //邻接矩阵记录路径 
int del[maxn]; //删除标记 
int vst[maxn]; //记录已经访问的结点 

void dfs(int x) {
	printf("%d ", x);
	vst[x] = 1;
	int i;
	for (i = 0; i < n; ++i) {
		if (!map[x][i] || del[i] || vst[i]) { //没有边,删除,或者访问过 
			continue;
		}
		dfs(i); //继续深搜 
	}
}

void bfs(int x) {
	clear();
	push(x);
	vst[x] = 1;
	printf("%d ", x);
	int i;
	while (size()) {
		int x = front(); //每次取出队首 
		pop();
		for (i = 0; i < n; ++i) {
			if (!map[x][i] || del[i] || vst[i]) { //没有边,删除,或者访问过 
				continue;
			}
			vst[i] = 1;
			printf("%d ", i);
			push(i); //将新的结点放入队列 
		}
	}
}

int main() {
	n = read();
	m = read();
	int i;
	for (i = 1; i <= m; ++i) {
		int x = read();
		int y = read();
		map[x][y] = map[y][x] = 1;
	} 
	memset(vst,0,sizeof vst);
	dfs(0);
	puts("");
	memset(vst,0,sizeof vst);
	bfs(0);
	puts("");
	del[read()] = 1; //读入被删除的点 
	memset(vst,0,sizeof vst);
	dfs(0);
	puts("");
	memset(vst,0,sizeof vst);
	bfs(0);
	puts("");
	return 0;
} 

复杂度分析

由于使用了邻接矩阵,每个点都遍历了一次别的点, 复杂度是 O(n²) 的

第二题:独立路径数计算

题目描述

【问题描述】

老张和老王酷爱爬山,每周必爬一次香山。有次两人为从东门到香炉峰共有多少条路径发生争执,于是约定一段时间内谁走过对方没有走过的路线多谁胜。

给定一线路图(无向连通图,两顶点之间可能有多条边),编程计算从起始点至终点共有多少条独立路径,并输出相关路径信息。

注:独立路径指的是从起点至终点的一条路径中至少有一条边是与别的路径中所不同的,同时路径中不存在环路。

img

【输入形式】

图的顶点按照自然数(0,1,2,...,n)进行编号,其中顶点0表示起点,顶点n-1表示终点。从标准输入中首先输入两个正整数n,e,分别表示线路图的顶点的数目和边的数目,然后在接下的e行中输入每条边的信息,具体形式如下:

<n> <e>

<e1> <vi1> <vj1>

<e2> <vi2> <vj2>

...

<en> <vin> <vjn>

说明:第一行<n>为图的顶点数,<e>表示图的边数;第二行<e1> <vi1> <vj1>分别为边的序号(边序号的范围在[0,1000)之间,即包括0不包括1000)和这条边的两个顶点(两个顶点之间有多条边时,边的序号会不同),中间由一个空格分隔;其它类推。

【输出形式】

输出从起点0到终点n-1的所有路径(用边序号的序列表示路径且路径中不能有环),每行表示一条由起点到终点的路径(由边序号组成,中间有一个空格分隔,最后一个数字后跟回车),并且所有路径按照字典序输出。

【样例输入】

6 8

1 0 1

2 1 2

3 2 3

4 2 4

5 3 5

6 4 5

7 0 5

8 0 1

【样例输出】

1 2 3 5

1 2 4 6

7

8 2 3 5

8 2 4 6

【样例说明】

样例输入构成的图如下:

graph8_1.png

输出的第一个路径1 2 3 5,表示一条路径,先走1号边(顶点0到顶点1),然后走2号边(顶点1到顶点2),然后走3号边(顶点2到顶点3),然后走5号边(顶点3到顶点5)到达终点。

题目大意

输出所有从 0 到 n - 1 的简单路径,并且还要按照字典序。

题目思路

显然一个 bfs 或者 dfs 我们就能得到所有这样的路径,但这样终究有点麻烦。

我们只要对边排好序,其实访问到可行的路径自动就是一个字典序的。

之后只要在 dfs 过程中用栈来维护答案就可以了。

代码实现

#include <stdio.h> 
#include <math.h>
#include <string.h>
#include <ctype.h>

inline int read() { //快速读入,可以放在自己的缺省源里面 
	int x = 0; //数字位 
	int f = 1; //符号位 
	char ch = getchar(); //读入第一个字符 
	while (!isdigit(ch)) { //不是数字 
		if (ch == '-') { //特判负号 
			f = -1;
		}
		ch = getchar();
	}
	while (isdigit(ch)) { //读入连续数字 
		x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1) 
		ch = getchar();
	}
	return x * f;
}

#define BIG 1000005
#define maxn 1005

typedef struct line{
	int to, bh;
} Line;

Line l[BIG];
int n, m, cnt;
int edge[maxn][maxn]; //记录每个点连向哪些边 
int edge_num[maxn]; //记录每个点连向边的数目 

int cmp(const void *a, const void *b) {
	int *p1 = (int*) a;
	int *p2 = (int*) b;
	return l[*p1].bh - l[*p2].bh;
}

int vst[maxn];
int sta[BIG]; // 用数组实现栈 
int top;

void dfs(int x) {
	vst[x] = 1; //标记这条路径上已经经过了 x 结点 
	int i;
	if (x == n-1) { //遍历到了所需结点 
		for (i = 1; i <= top; ++i) {
			printf("%d ", sta[i]);
		}
		puts("");
		vst[x] = 0;
		return;
	}
	for (i = 0; i < edge_num[x]; ++i) {
		int y = l[edge[x][i]].to;
		if (!vst[y]) { //没有遍历到 y 结点 
			sta[++top] = l[edge[x][i]].bh;
			dfs(y);
			--top;
		}
	}
	vst[x] = 0;
}

int main() {
	n = read();
	m = read();
	int i;
	for (i = 1; i <= m; ++i) { //读入并保存边信息 
		int e = read();
		int x = read();
		int y = read();
		l[++cnt] = (Line) {y, e}; 
		edge[x][edge_num[x]++] = cnt;
		l[++cnt] = (Line) {x, e}; 
		edge[y][edge_num[y]++] = cnt;
	} 
	for (i = 0; i < n; ++i) { //排序保证边按字典序 
		qsort(edge[i], edge_num[i], sizeof(int), cmp);
	}
	dfs(0);
	return 0;
} 

复杂度分析

由于涉及到路径的复制,且路径数随边数变化较大,复杂度可以非常大,但是由于边数限制,这种做法没有问题。

第三题:最少布线(图)

题目描述

【问题描述】

北航主要办公科研楼有新主楼、逸夫楼、如心楼、办公楼、图书馆、主楼、一号楼等等;。北航网络中心计划要给相关建筑物间铺设光缆进行网络连通,请给出用料最少的铺设方案。

编写程序输入一个办公区域分布图及建筑物之间的距离,计算出用料最少的铺设方案(只有一组最优解,不用考虑多组解)。要求采用Prim或Kruskal算法实现。

【输入形式】

办公区域分布图的顶点(即建筑物)按照自然数(0,1,2,n-1)进行编号,从标准输入中首先输入两个正整数,分别表示线路图的顶点的数目和边的数目,然后在接下的行中输入每条边的信息,每条边占一行,具体形式如下:

...

即顶点vi和vj之间边的权重是weight,边的编号是id。

【输出形式】

输出铺设光缆的最小用料数,然后另起一行输出需要铺设的边的id,并且输出的id值按照升序输出。

【样例输入】

6 10

1 0 1 600

2 0 2 100

3 0 3 500

4 1 2 500

5 2 3 500

6 1 4 300

7 2 4 600

8 2 5 400

9 3 5 200

10 4 5 600

【样例输出】

1500

2 4 6 8 9

【样例说明】

样例输入说明该分布图有6个顶点,10条边;顶点0和1之间有条边,边的编号为1,权重为600;顶点0和2之间有条边,权重为100,其它类推。其对应图如下:

graph8_1.png

经计算此图的最少用料是1500,可以使图连通,边的编号是2 4 6 8 9。其对应的最小生成树如下:

graph8_3.png

题目大意

求最小生成树

题目思路

出于简单,我们当然使用 kruskal 实现这道题

同时为了实现这道题,我们需要用到并查集(见前文)。

代码实现

#include <stdio.h> 
#include <math.h>
#include <string.h>
#include <ctype.h>

inline int read() { //快速读入,可以放在自己的缺省源里面 
	int x = 0; //数字位 
	int f = 1; //符号位 
	char ch = getchar(); //读入第一个字符 
	while (!isdigit(ch)) { //不是数字 
		if (ch == '-') { //特判负号 
			f = -1;
		}
		ch = getchar();
	}
	while (isdigit(ch)) { //读入连续数字 
		x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1) 
		ch = getchar();
	}
	return x * f;
}

#define maxn 100005

typedef struct line {
	int id;
	int x;
	int y;
	int v;
} Line;

Line l[maxn];
int n;
int m;
int ans[maxn];
int prt[maxn];
int cnt;

int sort_value(const void *a, const void *b) { //按边权从小到大排 
	Line* p1 = (Line*) a;
	Line* p2 = (Line*) b;
	return p1->v - p2->v;
}

int sort_id (const void *a, const void *b){ //对 id 排序 
	int *p1 = (int*) a;
	int *p2 = (int*) b;
	return *p1 - *p2;
}

int getf(int x) { //找到最上面的父亲 
	return x == prt[x] ? x : (prt[x] = getf(prt[x]));
} 

void solve() {
	qsort(l, m, sizeof(Line), sort_value); //先按照边权给边排序
	int i;
	int tot = 0;
	for (i = 0; i < n; ++i) { //并查集初始化 prt 
		prt[i] = i;
	} 
	for (i = 0; i < m; ++i) {
		int f1 = getf(l[i].x); 
		int f2 = getf(l[i].y);
		if (f1 == f2) { //已经在一个集合中
			continue; 
		}
		ans[cnt++] = l[i].id; //当前边加入边集 
		tot += l[i].v; //计算总和 
		prt[f1] = f2; //更新并查集 
	}
	printf("%d\n", tot);
	qsort(ans, cnt, sizeof(int), sort_id); //输出时对 id 排序 
	for (i = 0; i < cnt; ++i) {
		printf("%d ", ans[i]);
	}
}


int main() {
	n = read();
	m = read();
	int i;
	for (i = 0; i < m; ++i) {
		l[i].id = read();
		l[i].x = read();
		l[i].y = read();
		l[i].v = read(); 
	}
	solve();
	return 0;
} 

复杂度分析

复杂度是 O(mlogm),即边排序的复杂度。

第四题:北京地铁乘坐线路查询

题目描述

【问题描述】

编写一个程序实现北京地铁最短乘坐(站)线路查询,输入为起始站名和目的站名,输出为从起始站到目的站的最短乘坐站换乘线路。注:1. 要求采用Dijkstra算法实现;2)如果两站间存在多条最短路径,找出其中的一条就行。

img

【输入形式】

文件bgstations.txt为数据文件(可从课程网站中课程信息处下载),包含了北京地铁的线路及车站信息。其格式如下:

<地铁线路总条数>

<线路1> <线路1站数>

<站名1> <换乘状态>

<站名2> <换乘状态>

...

<线路2> <线路2站数>

<站名1> <换乘状态>

<站名2> <换乘状态>

...

说明:文件第一行为地铁总线路数;第二行第一个数为某条地铁线线号(如,1为1号线),第二个数为该条地铁线的总站数(如1号线共有23站),两数之间由一个空格分隔;第三行两个数据分别为地铁站名及换乘状态(0为非换乘站,1为换乘站),两数据间由一个空格分隔;以下同,依次为该线地铁其它站信息。在一条线路信息之后是下条地铁线路信息,格式相同。若某条地铁线为环线,则首站与末站信息相同(如北京地铁2号线,首站信息“西直门 1” ,末站信息为“西直门 1”)。例如本题提供的bgstations.txt文件(可从课程网站中课程信息处下载)内容如下:

13

1 23

苹果园 0

古城 0

八角游乐园 0

八宝山 0

玉泉路 0

五棵松 0

万寿路 0

公主坟 1

军事博物馆 1

木樨地 0

南礼士路 0

复兴门 1

西单 1

...

2 19

西直门 1

积水潭 0

鼓楼大街 1

...

西直门 1

...

题目大意

在北京地铁的背景下,用 dijkstra 实现最短路并且将路径输出。

其实并没有用 dijkstra 而是用了 SPFA,具体见上文。

题目思路

这个题似乎存在不是换乘站但是同时在两条线路上的点,但是由于不涉及到测试数据,这里就不考虑了因为考虑了我也错了

真要考虑的话,可能需要使用分层图来实现这道题,这就留给同学们自行了解了。

大体思路是利用链式前向星从 2 开始编号,异或得到相反边的性质来保存整个路径,最后处理输出。

最短路是用 SPFA 实现的,整个题目不简单,请仔细阅读代码并深入思考。

代码实现

#include<stdio.h> 
#include<math.h>
#include<string.h>
#define maxn 2005

struct point{
	char s[30];
} p[maxn];

int tot;

int newnode(char *s) { //新建结点 
	++tot;
	strcpy(p[tot].s, s);
	return tot;
}

int check(char *s) { //检查当前站是否在之前出现过,并返回编号 
	int i;
	for (i = 1; i <= tot; ++i) {
		int t = strcmp(p[i].s, s);
		if (t == 0) {
			return i;
		}
	}
	return newnode(s);
} 

#define maxm 100005
typedef struct line{ //借助链式前向星来组织图结构 
	int to;
	int nxt;
	int bh;
} Line;

Line e[maxm];
int h[maxn];
int cnt = 1;

void adde(int x, int y, int bh) { //cnt 从 2 开始编号,便于后续操作 
	e[++cnt] = (Line){y, h[x], bh};
	h[x] = cnt;
}

int q[maxn]; //为 SPFA 实现各种队列操作 
int head;
int tail;

void clear() { //清空队列 
	head = 1;
	tail = 0;
}

int front() { //取出队首 
	return q[head];
} 

void pop() { //弹出队首 
	++head;
}

void push(int x) { //放值进入队尾 
	q[++tail] = x;
}

int size() { //获得队列大小 
	return tail - head + 1;
}

#define INF 1e9
int vst[maxn];
int dis[maxn];
int from[maxn];

void Print(int s,int t){
	int x = s;
	int num = 0;
	int lst = -1;
	while (x != t) { //未到达目的地 
		int to = from[x]; //取出 x 记录的边 
		if(e[to].bh != lst){ //bh 出现变化,说明出现换乘 
			if (num) { //num有值,说明在这条线上走过(特判起始) 
			 	printf("-%d(%d)-", lst, num);
			}
			printf("%s", p[x].s); //输出换乘站名 
			num=0;
			lst=e[to].bh;
		}
		++num;
		x=e[to].to; //在记录的边上前进 
	}
	if(num) { //特判最后一条线 
		printf("-%d(%d)-", lst, num);
	}
	printf("%s",p[t].s); //输出最后一站站名 
}

void solve() {
	char temp[30];
	scanf("%s", temp);
	int s = check(temp);
	scanf("%s", temp);
	int t = check(temp); //获得起点终点
	int i;
	for (i = 1; i <= tot; ++i) { //初始化距离为无穷大 
		dis[i] = INF;
	} 
	dis[t] = 0; 
        clear();
	push(t); //从 t 开始反向找,便于找到路径 
	vst[t] = 1;
	while (size()) { // SPFA
		int x = front();
		pop();
		vst[x] = 0;
		int i;
		for (i = h[x]; i; i = e[i].nxt) {
			int y = e[i].to;
			if (dis[y] > dis[x] + 1) {
				dis[y] = dis[x] + 1;
				from[y] = i ^ 1; //编号从 2 开始,异或可以得到反向边( 
				if (!vst[y]) {
					push(y); 
					vst[y] = 1; 
				} 
			}
		}
	}
	Print(s, t);
}

int main() {
	FILE* IN = fopen("bgstations.txt", "r");
	int T;
	fscanf(IN, "%d", &T);
	int bh;
	int m;
	while (T--) {
		fscanf(IN, "%d%d", &bh, &m); //站编号及个数
		char s[30];
		int ty;
		int lst = -1; //记录上一个站
		int nw;
		int i;
		for (i = 1; i <= m; ++i)  {
			fscanf(IN, "%s%d", s, &ty);
			nw = check(s);
			if(~lst) { //取反操作,等价于 lst != -1 
				adde(nw, lst, bh); //双向边 
				adde(lst, nw, bh);
			}
			lst=nw;
		}
	}
	solve();
	return 0;
}

复杂度分析

复杂度是 SPFA 的复杂度,会随着图的性质变化, 最坏是 O(VE) //点集 * 边集

摇起来

大巴黎,咚咚咚

posted @ 2021-06-17 00:34  L_RUA  阅读(554)  评论(0编辑  收藏  举报