摘要
本文针对三个问题:排序重构问题、实现单源最短路径计算问题、课程的拓扑排序问题进行了问题分析与设计算法求解。
针对问题一:本文采用数据结构线性表的顺序存储结构存储给定数据,通过枚举算法计算出A数组中各元素间的差值得到数组D;通过枚举算法在D中找出符合条件的元素构造出数组A。
针对问题二:本文采用邻接矩阵以及邻接表的数据结构,分别使用了Dijkstra、Floyd算法计算出给定起点至其它各点的最短距离及最短路径,并对Dijkstra算法进行堆优化,使其时间复杂度和空间复杂度进一步优化,从而能处理大规模输入情况下的计算。
针对问题三:本文采用邻接表的数据结构,根据输入的课程及各课程间的修读顺序约束关系建立有向图,然后通过拓扑排序算法设计出程序计算出各课程的修读顺序的拓扑排序序列。
关键词:枚举;单源最短路径;Dijkstra;Floyd;拓扑排序;
一、排序重构问题
1.1 题目描述与分析
令
A
A
A为一个由
N
N
N个已特殊排序数组成的数列:
A
1
,
A
2
,
…
,
A
N
A_1,A_2,…,A_N
A1,A2,…,AN,其中
A
1
=
0
A_1=0
A1=0。令
D
D
D为
N
×
(
N
−
1
)
/
2
N\times (N-1)/2
N×(N−1)/2个数(定义为
D
i
j
=
A
i
−
A
j
(
i
>
j
)
D_{ij}=A_i-A_j(i>j)
Dij=Ai−Aj(i>j))组成的数列。例如,
A
=
0
,
1
,
5
,
8
A=0,1,5,8
A=0,1,5,8,那么
D
=
1
,
3
,
4
,
5
,
7
,
8
D=1,3,4,5,7,8
D=1,3,4,5,7,8。请完成:
(1)编写程序,根据
A
A
A构造
D
D
D;
(2)编写程序,构造与
D
D
D相对应的某一个数列
A
A
A,注意
A
A
A不是唯一的。
经过对上述题目的分析,选择采用枚举的算法求解本题,具体求解过程如下:
1.2 采用C++语言定义相关的数据类型
本题采用线性表的顺序存储结构存储数组A和数组D的元素。采用C++语言定义相关的数据类型如下:
vector<int> A;//数组A
vector<int> D;//数组D
int idx;//数组A的长度
1.3 算法设计
(1)针对问题一,采用枚举解法,将数组 A A A中的所有元素两两做差运算 A i − A j ( i > j ) A_i-A_j(i>j) Ai−Aj(i>j),然后将差值存入数组 D D D中,最后根据数组 A A A的排序方式将 D D D进行排序得到根据 A A A构造的 D D D数组;
(2)针对问题二,采用枚举解法,首先根据题意和已知的数组 D D D的大小,计算出数组 A A A的大小 i d x idx idx,然后由题意可知元素 0 0 0必定为数组 A A A的第一个元素,数组 D D D中的最大值必定为数组 A A A的最后一个元素,因此初始化将 0 0 0和数组 D D D的最大值存入 A A A中,并在 D D D中移除最大值元素。按从大到小的顺序从 D D D中枚举每个元素 x x x,若 A A A中的非零元素减去 x x x的值都在 D D D中出现且不等于 x x x本身,则将 x x x存入 A A A中,然后移除 D D D中所有在 A A A中能产生的差值与元素 x x x,直到 A A A的大小达到 i d x idx idx后得到结果。
1.4 函数的调用关系图
求解排序重构问题算法的函数调用关系图如下图所示:
1.5 调试分析
(1)问题分析及解决方法:
经过调试发现对于输入数据:2 3 5 6 8 9 11 14 16 17 19 24 30 33 35
执行
D
D
D构造
A
A
A的算法时输出结果为:0 19 24 30 33 35
,原因是由于最初设计函数dtoa()
时没有考虑在
D
D
D中移除
A
A
A中能产生的差值,经过修改该问题已解决,可得到正确结果:0 16 24 30 33 35
。对于输入数据1 3 4 5 7 8
执行
D
D
D构造
A
A
A的算法时输出结果为:0 4 7 8
,原因是未排除
A
A
A中元素与待插入元素的差值等于该元素本身的情况,如
A
A
A中已有元素{0,7,8}
,当元素
4
4
4待插入时,由于8-4
与7-4
的值都在
D
D
D中,因此将
4
4
4插入了
A
A
A中,经过修改该问题已解决,可得到正确结果:0 3 7 8
.
(2)算法的时间复杂度与空间复杂度分析:
该算法的函数atod()
需要遍历两次数组
A
A
A,求出每两个元素间的差值,因此时间复杂度为
O
(
N
2
)
O(N^2)
O(N2),函数dtoa()
需要遍历
D
D
D中的元素,对于每个元素还要遍历一次
A
A
A中的元素检验差值是否存在于
D
D
D中,因此时间复杂度为
O
(
N
2
)
O(N^2)
O(N2),综上该算法的时间复杂度为
O
(
N
2
)
O(N^2)
O(N2)。该算法需要用到两个数组存储元素,存储结构为线性表的线性存储结构,因此空间复杂度为
O
(
N
)
O(N)
O(N)。
1.6 测试结果
设计第一组测试用例测试该算法求解两题的执行结果是否与正确结果一致,测试结果如下图所示,经测试运行结果与正确结果相符。
设计第二组测试用例测试该算法求解规模较大的输入时运行效率是否达到预期效果,测试结果如下图所示,经测试运行效率达到预期效果。
1.7 源程序
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
vector<int> A, D;
int n, x, cho;
//O(n^2)
void atod()
{
for (int len = 1; len < A.size(); len++)
for (int i = 0; i + len < A.size(); i++)
D.push_back(A[i + len] - A[i]);
if (A[1] > A[0]) sort(D.begin(), D.end());
else sort(D.begin(), D.end(), greater<int>());
}
//O(n^2)
void dtoa()
{
int idx;//A的大小
for (idx = 1; idx * idx < D.size() * 2; idx++)
if (idx * (idx + 1) == D.size() * 2) break;
idx++;
//初始化A先将0和最大值放入A中
A.push_back(0);
A.push_back(D[D.size() - 1]);
D.erase(D.end() - 1);//在D中去掉最大值
while (A.size() < idx)
{
for (int i = D.size() - 1; i >= 0; i--)
{
int j;
for (j = A.size() - 1; j > 0; j--)
{
if (find(D.begin(), D.end(), A[j] - D[i]) == D.end())
break;
if (A[j] - D[i] == D[i])
break;
}
if (j == 0)
{
int temp = D[i];
for (int k = A.size() - 1; k > 0; k--)//去掉D中所有在A中能产生的差值
D.erase(find(D.begin(), D.end(), A[k] - temp));
D.erase(find(D.begin(), D.end(), temp));//去掉插入A中的值
A.insert(A.begin() + 1, temp);
break;
}
/*if (j == 0)//错误代码
{
A.insert(A.begin() + 1, D[i]);
D.erase(D.begin() + i);
break;
}*/
}
}
}
int main()
{
cin >> cho;
switch (cho)
{
case 1:
cin >> n;
for (int i = 0; i < n; i++) { cin >> x; A.push_back(x); }
atod();
for (int i = 0; i < D.size(); i++) cout << D[i] << ' ';
break;
case 2:
cin >> n;
for (int i = 0; i < n; i++) { cin >> x; D.push_back(x); }
dtoa();
for (int i = 0; i < A.size(); i++) cout << A[i] << ' ';
break;
}
return 0;
}
/*
输入描述
第一行输入两个数k, n
若k=1则表示输入长度为n的数列A,构造出对应的数列D
若k=2则表示输入长度为n的数列D,构造出对应的数列A
第二行输入n个数
输入样例1
1 4
0 3 7 8
输出样例1
1 3 4 5 7 8
输入样例2
2 6
1 3 4 5 7 8
输出样例2
0 3 7 8
输入样例3
1 6
0 16 24 30 33 35
输出样例3
2 3 5 6 8 9 11 14 16 17 19 24 30 33 35
输入样例4
2 15
2 3 5 6 8 9 11 14 16 17 19 24 30 33 35
输出样例4
0 16 24 30 33 35
*/
二、实现单源最短路径计算问题
2.1 题目描述与分析
编写一个实现单源最短路径的计算程序,在有向带权图中获得两点间最短路径的计算。(以一个公路最短路径导航为例进行计算)
经过对上述题目的分析,选择分别采用Dijkstra、堆优化Dijkstra以及Floyd算法求解本题,具体求解过程如下:
2.2 采用C++语言定义相关的数据类型
(1)定义Dijkstra算法相关数据类型:
采用邻接矩阵存有向图,邻接矩阵用二维数组实现,mp[i][j]
表示顶点
i
i
i至顶点
j
j
j的距离。采用C++语言定义相关的数据类型如下:
int N = 1010;//图的最大顶点数
int INF = 0x3f3f3f3f;//无穷大变量
int mp[N][N];//图的邻接矩阵
int dis[N];//起点至其它各点的最短距离
int vis[N];//标记某点是否被访问过
int f[N];//表示最短路径中某顶点的父顶点
int n;//顶点个数
int m;//边的条数
int s;//起点编号
int t;//终点编号
(2)定义堆优化版Dijkstra算法相关数据类型:
采用邻接表存有向图,用一维数组实现,i = h[node]
表示顶点
n
o
d
e
node
node所连接的第一条边
i
i
i,j = e[i]
表示边
i
i
i连接的另一个顶点,d[i]
表示边i的权值。采用C++语言定义相关的数据类型如下:
int N = 10010; //图的最大顶点数
int M = 10010;//图的最大边数
int INF = 0x3f3f3f3f;//无穷大变量
int e[M];//邻接表中某条边所连接的另一个点
int ne[M];//邻接表中某条边同起点的下一条边
int h[N];//邻接表中的起点编号
int d[M];//边的权值
int dis[N];//起点至其它各点的最短距离
int vis[N];//标记某点是否被访问过
int f[N];//表示最短路径中某顶点的父顶点
int n;//顶点个数
int m;//边的条数
int s;//起点编号
int t;//终点编号
(3)定义Floyd算法相关数据类型:
采用邻接矩阵存有向图,邻接矩阵用二维数组实现,mp[i][j]
表示顶点
i
i
i至顶点
j
j
j的距离。采用C++语言定义相关的数据类型如下:
int N = 1010;//图的最大顶点数
int INF = 0x3f3f3f3f;//无穷大变量
int mp[N][N];//图的邻接矩阵
int path[N][N];//i到j的最短路径的父顶点
int n;//顶点个数
int m;//边的条数
int s;//起点编号
int t;//终点编号
2.3 算法设计
(1)Dijkstra算法设计过程如下:
假设图的各顶点与边的属性如上图所示,设起点为
A
A
A,求
A
A
A到其他各点的最短距离,进行Dijkstra算法的解题过程的设计分析。
首先建立结果顶点集
S
S
S,其中存放起点
A
A
A至其他顶点的距离(用顶点编号(最短距离)的形式表示),初始化先将起点
A
A
A加入至集合中,即S={A(0)}
;然后建立距离顶点集
D
D
D,表示起点到
D
D
D集合中顶点的最短距离,若无法到达则用
∞
∞
∞表示,即
D
D
D的初始化内容为D={B(4),C(3),D(7),E(∞),F(∞)}
。
开始循环,每次从
D
D
D中取出距离最短的点加入
S
S
S中,并更新
D
D
D中其他顶点的距离:
第一次循环:S={A(0),C(3)};D={B(4),D(7),E(∞),F(7)}
;
第二次循环:S={A(0),C(3),B(4)};D={D(6),E(9),F(7)}
;
第三次循环:S={A(0),C(3),B(4),D(6)};D={E(7),F(7)}
;
第四次循环:S={A(0),C(3),B(4),D(6),E(7)};D={F(7)}
;
第五次循环:S={A(0),C(3),B(4),D(6),E(7),F(7)};D={}
;
算法结束后即求得
A
A
A点至其他各点的最短距离分别为:
A->A
距离为0
;A->B
距离为4
;A->C
距离为3
;
A->D
距离为6
;A->E
距离为7
;A->F
距离为7
;
对于算法的代码分析:首先初始化dis[s]=0
,然后遍历循环
n
n
n次,每次选出顶点
n
o
d
e
node
node,使得dis[node]
最小,并标记
n
o
d
e
node
node已访问;接着遍历
n
n
n个顶点,更新以
n
o
d
e
node
node为中间点的起点至顶点
j
j
j的距离的最小值。
(2)堆优化版Dijkstra算法设计:
由于普通版Dijkstra算法的时间复杂度与空间复杂度的缺点,针对大规模数据(如
N
=
M
=
1
0
5
N=M=10^5
N=M=105)将会超时与爆内存,因此对其进行堆优化。
将存储图的数据结构改为邻接表,然后在算法的执行过程中构建一个存放pair<int,int>
(以下简称
P
P
P)类型的小根堆(本文采用C++STL中的priority_queue
实现),首先将起点{0,s}
加入堆中(
P
P
P的
f
i
r
s
t
first
first元素为起点至
s
e
c
o
n
d
second
second元素的最短距离,
s
e
c
o
n
d
second
second元素为顶点标号),然后循环每次取出堆顶元素
n
o
d
e
node
node,遍历该点所连接的所有边,若该点
n
o
d
e
node
node至点
j
j
j的距离小于dis[j]
,则更新dis[j]
,并将{dis[j],j}
加入堆中。
(3)Floyd算法设计:
Floyd算法通过三重循环,最外重循环
k
k
k遍历每个点,代表中介点,第二层和最内层循环
i
,
j
i,j
i,j遍历每个点,循环比较mp[i][j]
与mp[i][k] + mp[k][j]
的大小,用较小值更新mp[i][j]
(即用以
k
k
k为中介点的
i
i
i到
j
j
j的距离值)。循环后所得的mp[i][j]
即为顶点
i
i
i至顶点
j
j
j的最短路径。
2.4 函数的调用关系图
(1)调用Dijkstra算法的函数调用关系图如下图所示:
(2)调用Floyd算法的函数调用关系图如下图所示:
2.5 调试分析
(1)问题分析及解决方法:
处理大规模数据问题:经过对普通版Dijkstra算法的分析及测试,发现当输入数据量大到一定程度时将无法正常运行或运行效率大幅降低,因此针对此问题进行了优化处理,即采用了小根堆优化的Dijkstra算法。
考虑推广至任意两点间距离问题:由于Dijkstra算法只能针对给定起点的最短路径计算问题(单源最短路径问题),每次查询不同的起点都需要执行一次算法过程,对于多次查询的效率并不高,因此若针对于多次查询的最短路径问题,可采用Floyd算法求解。
(2)算法的时间复杂度及空间复杂度分析:
普通版Dijkstra算法:由于该算法需要循环
N
N
N次,每次循环中首先需要循环
N
N
N次找出
d
i
s
dis
dis最小的顶点
n
o
d
e
node
node,然后循环
N
N
N个顶点进行
d
i
s
dis
dis的更新操作,因此时间复杂度为
O
(
N
2
)
O(N^2)
O(N2)。该算法用到了邻接矩阵的数据结构,因此需要建立二维数组mp[N][N]
,空间复杂度为
O
(
N
2
)
O(N^2)
O(N2);
堆优化版Dijkstra算法:该算法需要遍历每条边
(
M
)
(M)
(M),最坏情况下要进行
N
N
N次小根堆的push
操作
(
l
o
g
N
)
(logN)
(logN),因此时间复杂度为
O
(
M
×
l
o
g
N
)
O(M\times logN)
O(M×logN)。由于该算法中将存图的数据结构改为了邻接表,因此空间复杂度为
O
(
M
)
O(M)
O(M);
Floyd算法:该算法的核心部分为三重循环,每层循环都是遍历
N
N
N个顶点,时间复杂度为
O
(
N
3
)
O(N^3)
O(N3)。该算法所用到的数据结构也采用了邻接矩阵,因此空间复杂度为
O
(
N
2
)
O(N^2)
O(N2)。
2.6 测试结果
设计第一组测试用例测试三种算法求解最短路径的执行结果是否与正确结果一致,测试结果如下图所示,经测试运行结果与正确结果相符。
设计第二组测试用例测试三种算法求解规模较大的输入时运行效率是否达到预期效果,测试结果如下图所示,经测试运行效率达到预期效果。
2.7 源程序
//普通版Dijkstra(O(n^2))
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int mp[N][N], f[N], dis[N], vis[N];
int n, m, s, t;
void dijkstra(int s, int t)
{
memset(f, -1, sizeof f);
memset(dis, INF, sizeof dis);
memset(vis, 0, sizeof vis);
dis[s] = 0;
for (int i = 1; i <= n; i++)
{
int minn = INF, node = -1;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && dis[j] < minn)
{
minn = dis[j];
node = j;
}
}
if (node == -1) return;
vis[node] = 1;
for (int j = 1; j <= n; j++)
if (!vis[j] && dis[node] + mp[node][j] < dis[j])
{
dis[j] = dis[node] + mp[node][j];
f[j] = node;
}
}
}
int main()
{
memset(mp, INF, sizeof mp);
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
mp[x][y] = min(mp[x][y], z);
}
while (1)
{
cin >> s >> t;
if (s == -1 && t == -1) break;
dijkstra(s, t);
for (int i = 1; i <= n; i++)
if(dis[i] != INF) cout << dis[i] << ' ';
else cout << -1 << ' ';
cout << endl;
if (dis[t] != INF)
{
cout << dis[t] << endl;
stack<int> stk;
stk.push(t);
while (f[t] != -1)
{
stk.push(f[t]);
t = f[t];
}
while (!stk.empty())
{
cout << stk.top() << ' ';
stk.pop();
}
cout << endl;
}
else cout << -1 << endl;
}
}
//堆优化版Dijkstra(O(mlogn))
/*#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
using namespace std;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const int N = 510, M = 1010;
int e[M], ne[M], h[N], d[M], idx;
int dis[N], vis[N], f[N];
int n, m, s, t;
void add(int x, int y, int z)
{
e[idx] = y, ne[idx] = h[x], d[idx] = z, h[x] = idx++;
}
void dijkstra(int s, int t)
{
priority_queue<P, vector<P>, greater<P> > Q;
Q.push({ 0, s });
memset(dis, INF, sizeof dis);
memset(vis, 0, sizeof vis);
memset(f, -1, sizeof f);
dis[s] = 0;
while (!Q.empty())
{
int node = Q.top().second;
Q.pop();
if (vis[node]) continue;
vis[node] = 1;
for (int i = h[node]; ~i; i = ne[i])
{
int j = e[i];
if (dis[node] + d[i] < dis[j])
{
dis[j] = dis[node] + d[i];
f[j] = node;
Q.push({ dis[j], j });
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
}
while (1)
{
cin >> s >> t;
if (s == -1 && t == -1) break;
dijkstra(s, t);
for (int i = 1; i <= n; i++)
if(dis[i] != INF) cout << dis[i] << ' ';
else cout << -1 << ' ';
cout << endl;
if (dis[t] != INF)
{
cout << dis[t] << endl;
stack<int> stk;
stk.push(t);
while (f[t] != -1)
{
stk.push(f[t]);
t = f[t];
}
while (!stk.empty())
{
cout << stk.top() << ' ';
stk.pop();
}
cout << endl;
}
else cout << -1 << endl;
}
}*/
//Floyd(O(n^3))
/*#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int mp[N][N], path[N][N];
int n, m, s, t;
void floyd()
{
for (int i = 1; i <= n; i++) mp[i][i] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
path[i][j] = j;
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (mp[i][k] + mp[k][j] < mp[i][j])
{
mp[i][j] = mp[i][k] + mp[k][j];
path[i][j] = path[i][k];
}
}
int main()
{
memset(mp, INF, sizeof mp);
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
mp[x][y] = min(mp[x][y], z);
}
floyd();
while (1)
{
cin >> s >> t;
if (s == -1 && t == -1) break;
for (int i = 1; i <= n; i++)
if (mp[s][i] != INF) cout << mp[s][i] << ' ';
else cout << -1 << ' ';
cout << endl;
if (mp[s][t] != INF)
{
cout << mp[s][t] << endl;
cout << s << ' ';
int p = path[s][t];
while (p != t)
{
cout << p << ' ';
p = path[p][t];
}
cout << t << endl;
}
else cout << -1 << endl;
}
}*/
/*
输入描述
第一行输入两个整数n,m表示顶点数和边数
接下来m行每行输入三个整数u,v,w表示u到v有一条长度为w的有向边
然后每行输入两个整数x,y表示查询xy之间的最短路径(以(-1,-1)查询表示查询结束)
输出描述
第一行输出x点至其它点的最短距离(若无法到达则距离为-1)
第二行若x点可达y点,则输出x至y的最短距离,然后在第三行输出路径,否则第二行只输出-1即可
输入样例1
4 6
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
1 3
1 4
-1 -1
输出样例1
0 2 4 3
4
1 2 3
0 2 4 3
3
1 2 4
输入样例2
15 25
1 2 2
2 7 2
2 5 1
1 9 5
3 7 3
1 10 4
12 2 7
13 15 3
6 14 6
4 11 5
15 8 4
14 11 7
3 8 8
3 9 6
13 6 8
4 13 7
7 8 3
4 6 5
9 11 4
9 15 7
4 1 6
9 12 5
12 13 3
9 4 7
7 12 8
1 8
2 5
2 11
9 3
5 12
-1 -1
输出样例2
0 2 -1 12 3 17 4 7 5 4 9 10 13 23 12
7
1 2 7 8
-1 0 -1 -1 1 21 2 5 -1 -1 34 10 13 27 16
1
2 5
-1 0 -1 -1 1 21 2 5 -1 -1 34 10 13 27 16
34
2 7 12 13 6 14 11
13 12 -1 7 13 12 14 11 0 17 4 5 8 18 7
-1
-1 -1 -1 -1 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1
*/
三、程的拓扑排序问题
3.1 题目描述与分析
根据课程修读顺序,实现一个拓扑排序,获得一个课程安排结果(以你的专业课程修读为例)。
3.2 采用C++语言定义相关的数据类型
本题使用邻接表存储有向图,使用顺序表存放拓扑排序的结果,采用C++语言定义相关的数据类型如下:
int N = 1010;//最大顶点数(课程数)
int M = 10010;//最大的边数(课程修读顺序关系数)
int e[M];//邻接表中某条边所连接的另一个点
int ne[M];//邻接表中某条边同起点的下一条边
int h[N];//邻接表中的起点编号
int w[N];//每个顶点的入度
int n;//顶点个数
int m;//边的条数
vector<int> res;//存放拓扑排序结果
3.3 算法设计
假设各课程信息如下表所示:
课程编号 | 课程名 | 先修课程 |
1 | 高等数学 | 无 |
2 | 模拟电路 | 高等数学、数字电路 |
3 | 数字电路 | 高等数学 |
4 | 数据结构 | 高等数学、C程序设计 |
5 | 操作系统 | 数字电路、数据结构、C程序设计 |
6 | C程序设计 | 无 |
根据上表信息,可构建出一个有向无环图如下图所示,要对课程进行拓扑排序,即对图中所有结点进行排序,使得没有一个结点指向它前面的结点。
算法分析:首先统计所有结点的入度,对于入度为
0
0
0的结点可以将其分离出来,然后把这个结点所指向的其它结点的入度
−
1
-1
−1,重复此操作,直到所有结点都被分离出来。如果最后存在入度不为
0
0
0的结点,则说明有环,不存在拓扑排序。算法的执行流程如下所示:
由上图可知,结点
1
1
1的入度为
0
0
0,因此将结点
1
1
1分离出来加至
r
e
s
res
res中,然后将结点
2
,
3
,
4
2,3,4
2,3,4的入度
−
1
-1
−1,如下图所示:
此时观察到结点 3 3 3的入度变为 0 0 0了,因此可以将结点 3 3 3分离出来加至 r e s res res中,将结点 2 , 5 2,5 2,5的入度 − 1 -1 −1,如下图所示:
重复以上操作(见下图),最终即可得到拓扑排序结果:res={1,3,2,6,4,5}
,即课程的修读顺序应为:高等数学、数字电路、模拟电路、C程序设计、数据结构、操作系统。
3.4 函数的调用关系图
调用拓扑排序算法的函数调用关系图如下图所示:
3.5 调试分析
(1)问题分析及解决方法:
拓扑排序结果的相对顺序问题:经过对最初设计的拓扑排序函数
t
o
p
o
S
o
r
t
topoSort
topoSort输入数据进行计算,发现得出结果为res={1,6,3,4,2,5}
,因此考虑是否能以字典序排列再对此结果进行优化,故采用小根堆(STL中的priority_queue
),设计出修正后的拓扑排序函数
m
i
n
T
o
p
o
S
o
r
t
minTopoSort
minTopoSort,运行结果为res={1,3,2,6,4,5}
。
(2)拓扑排序算法的时间复杂度与空间复杂度分析:
该算法每次在入度为
0
0
0的集合中取结点,需要执行
N
N
N次,由于输出每个结点的同时还要删除以它为起点的边,因此需要执行
M
M
M次,故此拓扑排序算法的时间复杂度为线性的。该算法存图所采用的数据结构为邻接表,空间复杂度也为线性的。
3.6 测试结果
设计第一组测试用例测试该拓扑排序算法求解拓扑序列的执行结果是否与正确结果一致,测试结果如下图所示,经测试运行结果与正确结果相符。
设计第二组测试用例测试该拓扑排序算法求解规模较大的输入时运行效率是否达到预期效果,测试结果如下图所示,经测试运行效率达到预期效果。
3.7 源程序
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N = 1010, M = 10010;
int e[M], ne[M], h[N], idx;
int n, m, w[N];
vector<int> res;
void add(int u, int v)
{
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
//O(n+m)
void topoSort()
{
queue<int> Q;
for (int i = 1; i <= n; i++)
if (!w[i]) Q.push(i);//初始化先将入度为0的所有结点入队列
while (!Q.empty())
{
int node = Q.front(); Q.pop();//循环每次出队列一个结点
res.push_back(node);//存入拓扑排序结果中
for (int i = h[node]; ~i; i = ne[i])//将此结点所指向的其它结点的入度-1
if (!--w[e[i]]) Q.push(e[i]);//如果其它结点入度也变为0则将其入队
}
}
//字典序从小到大
void minTopoSort()
{
priority_queue<int, vector<int>, greater<int> > Q;
for (int i = 1; i <= n; i++)
if (!w[i]) Q.push(i);
while (!Q.empty())
{
int node = Q.top(); Q.pop();
res.push_back(node);
for (int i = h[node]; ~i; i = ne[i])
if (!--w[e[i]]) Q.push(e[i]);
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
w[v]++;
}
minTopoSort();
if (res.size() == n) for (int i = 0; i < res.size(); i++) cout << res[i] << ' ';
else cout << "The input array can't topological sort";
return 0;
}
/*
输入描述
第一行输入两个正整数n,m分别表示课程数量(课程编号为1-n)和课程间的修读顺序约束关系
接下来m行每行输入两个正整数u,v表示u为v的先修课程
输出描述
输出共一行,输出课程的拓扑排序结果。
输入样例1
6 8
1 2
1 3
1 4
3 2
3 5
4 5
6 4
6 5
输出样例1
1 3 2 6 4 5
输入样例2
6 6
6 3
6 1
5 1
5 2
3 4
4 2
输出样例2
5 6 1 3 4 2
输入样例3
10 13
9 10
8 9
2 6
1 2
1 5
5 8
5 4
3 7
1 4
3 5
7 10
5 6
9 2
输出样例3
1 3 5 4 7 8 9 2 6 10
*/