GitHub同步更新(已分类):Data_Structure_And_Algorithm-Review
公众号:URLeisure 的复习仓库
公众号二维码见文末
以下是本篇文章正文内容,下面案例可供参考。
如何求源点到其他各顶点的最短路径?
给定有向带权图 G = (V,E),其中每条边的权值是非负实数。给定一个顶点 V ,称为源点
。
现在要计算 从源点到其他各个顶点的最短路径长度
。
在带权图中,两个顶点的路径长度指它们之间的路径上各边的权值之和
。
Dijkstra 算法是解决单源最短路径问题的贪心算法。
它先求出长度最短的一条路径,再参照该最短路径求出长度次短的一条路径,直到求出从源点到其他各个顶点的最短路径
Dijkstra 算法采用的贪心策略是 选择一条特殊路径,其长度为最短的路径,将其连接的 V-S 中的顶点加入集合 S,同时更新数组 dist[]。一旦 S 包含了所有顶点,dist[] 就是从源点到所有其他顶点之间的最短路径长度
。
G.Edge[ ][ ]
;u、i 是顶点
),否则 G.Edge[u][i] = ∞;dist[i]
来记录从源点到顶点 i 的最短路径长度;p[i]
来记录最短路径上 i 顶点的前驱。"借东风"
。"找最小路径"
中已经找到了源点 t 的最短路径,那么对于集合 V-S 中所有与顶点 t 相邻的顶点 j,都可以借助 t 走捷径。"找最小路径"
。由此,可求得从源点 u 到图 G 的其余各个顶点的最短路径及长度,也可以通过数组 p[ ] 逆向找到最短路径上经过的顶点。
如图,利用 Dijkstra 算法计算源点 1 到其他顶点的最短路径。
数据结构
:构建邻接矩阵 G.Edge[ ][ ], 有边则存储权值,无边则存储无穷,如图。初始化
:令集合 S = {1} 从 1 号顶点出发,V-S = {2,3,4,5},对于集合 V-S 中的所有顶点 x,初始化最短距离数组。dist[i]
记录从源点到 i 顶点的 最短路径长度
。
p[i]
记录最短路径上 i 顶点的 前驱
。
dist[i] = G.Edge[1][i] (1 是源点),dist[1] = 0.
初始化 p[ ] 数组。
找最小
:在集合 V-S = {2,3,4,5} 中,用贪心策略来寻找 V-S 集合中 dist[ ] 最小的顶点 t。加入 S 集合
:通过上图,我们得知 t 为 2,4,我们先将 2 加入 S 集合 S = {1,2},同时更新 V-S = {3,4,5}。借东风
:找到了源点到 t 的最短路径,那么对于集合 V-S 中所有 t 的邻接点 j,都可以借助 t 走捷径。2 的邻接点只有 5
,dist[2] + G.Edge[2][5] = 5 < dist[5] = ∞,因此 更新 dist[5] = 5
。同时记录 5 的前驱 p[5] = 2
。找最小
:在集合 V-S = {3,4,5} 中,用贪心策略来寻找 V-S 集合中 dist[ ] 最小的顶点 t。加入 S 集合
:通过上图,我们得知 t 为 4,我们将 4 加入 S 集合 S = {1,2,4},同时更新 V-S = {3,5}。借东风
:找到了源点到 t 的最短路径,那么对于集合 V-S 中所有 t 的邻接点 j,都可以借助 t 走捷径。4 的邻接点有 1,2,3,5
,但在 V-S 集合中的邻接点只有 3,5。不更新 dist[3]
。不更新 dist[5]
。找最小
:在集合 V-S = {3,5} 中,用贪心策略来寻找 V-S 集合中 dist[ ] 最小的顶点 t。加入 S 集合
:通过上图,我们得知 t 为 3 和 5,我们先将 3 加入 S 集合 S = {1,2,3,4},同时更新 V-S = {5}。借东风
:找到了源点到 t 的最短路径,那么对于集合 V-S 中所有 t 的邻接点 j,都可以借助 t 走捷径。3 没有邻接点
,跳。找最小
:在集合 V-S = {5} 中,用贪心策略来寻找 V-S 集合中 dist[ ] 最小的顶点 t。加入 S 集合
:只剩一个节点了,我们得知 t 为 5,我们将 5 加入 S 集合 S = {1,2,3,4,5},同时更新 V-S = {}。算法结束
: V-S = {} 为空时,算法结束。如:
p[5] = 2,即 5 的前驱是 2;p[2] = 1,即 2 的前驱是 1;p[1] = -1,1 没有前驱。所以 从源点 1 到 5 的最短路径为 1 → 2 → 5
。
c++代码如下(示例):
#include
#include "stack"
using namespace std;
typedef string VexType;
const int MaxVnum = 1e2;
const int INF = 0x3f3f3f3f;
int dis[MaxVnum], p[MaxVnum];
int flag[MaxVnum];
struct AMGraph {
VexType Vex[MaxVnum];
int Edge[MaxVnum][MaxVnum];
int vexnum, edgenum;
};
int LocateVex(AMGraph G, VexType x) {
for (int i = 0; i < G.vexnum; ++i) {
if (x == G.Vex[i]) {
return i;
}
}
return -1;
}
void CreateAMGraph(AMGraph &G) {
VexType u, v;
int w;
cin >> G.vexnum >> G.edgenum;
for (int i = 0; i < G.vexnum; ++i) {
cin >> G.Vex[i];
}
for (int i = 0; i <= G.vexnum; ++i) {
for (int j = 0; j <= G.vexnum; ++j)
G.Edge[i][j] = INF;
}
while (G.edgenum--) {
cin >> u >> v >> w;
int i = LocateVex(G, u);
int j = LocateVex(G, v);
if (i != -1 && j != -1) {
G.Edge[i][j] = w;
} else {
G.edgenum++;
}
}
}
/**
*
* @param G 邻接矩阵
* @param u 源点的下标
*/
void Init(AMGraph G, int u) {
for (int i = 0; i < G.vexnum; ++i) {
dis[i] = G.Edge[u][i];
flag[i] = false;
if (dis[i] == INF) {
p[i] = -1;
} else {
p[i] = u;
}
}
}
/**
*
* @param G 邻接矩阵
* @param x 源点
*/
void Dijkstra(AMGraph G, VexType x) {
int u = LocateVex(G, x);
Init(G, u);
dis[u] = 0;
flag[u] = true;
for (int i = 0; i < G.vexnum; ++i) {
int temp = INF;
int t = u;
for (int j = 0; j < G.vexnum; ++j) {// 在集合 V-S 中找最小
if (!flag[j] && dis[j] < temp) {
t = j;
temp = dis[j];
}
}
if (t == u) {
return;
}
flag[t] = true;//加入 S 集合
for (int j = 0; j < G.vexnum; ++j) {//借东风
if (!flag[j] && G.Edge[t][j] < INF) {
if (dis[j] > dis[t] + G.Edge[t][j]) {
dis[j] = dis[t] + G.Edge[t][j];
p[j] = t;
}
}
}
}
}
/**
*
* @param G 邻接矩阵
* @param u 源点
*/
void FindPath(AMGraph G, VexType u) {
stack<int> s;
int x;
for (int i = 0; i < G.vexnum; ++i) {
x = p[i];
if (x == -1 && u != G.Vex[i]) {
cout << u << "无法到" << G.Vex[i];
continue;
}
while (x != -1) {
s.push(x);
x = p[x];
}
cout << u << "到" << G.Vex[i] << "的路径为:";
while (!s.empty()) {
cout << G.Vex[s.top()] << "---";
s.pop();
}
cout << G.Vex[i] << " " << "距离为:" << dis[i] << endl;
}
}
int main() {
AMGraph G;
VexType u;
CreateAMGraph(G);
cin >> u;
Dijkstra(G, u);
FindPath(G, u);
return 0;
}
/*
5 8
1 2 3 4 5
1 2 2
1 3 5
1 4 2
2 5 3
4 1 7
4 5 6
4 2 1
4 3 3
1
*/
java代码如下(示例):
import java.util.LinkedList;
import java.util.Scanner;
public class A {
private static Scanner sc = new Scanner(System.in);
private static final int MaxVnum = 100;
private static final int INF = Integer.MAX_VALUE;
private static boolean[] flag = new boolean[MaxVnum];
private static int[] dist = new int[MaxVnum];
private static int[] p = new int[MaxVnum];
private static class AMGraph {
String Vex[] = new String[MaxVnum];
int Edge[][] = new int[MaxVnum][MaxVnum];
int vexnum, edgenum;
}
private static int locateVex(AMGraph g, String x) {
for (int i = 0; i < g.vexnum; i++) {
if (g.Vex[i].equals(x)) {
return i;
}
}
return -1;
}
private static void createAMGraph(AMGraph g) {
String u, v;
int w;
g.vexnum = sc.nextInt();
g.edgenum = sc.nextInt();
for (int i = 0; i <= g.vexnum; i++) {
for (int j = 0; j <= g.vexnum; j++)
g.Edge[i][j] = INF;
}
for (int i = 0; i < g.vexnum; i++) {
g.Vex[i] = sc.next();
}
while (g.edgenum-- > 0) {
u = sc.next();
v = sc.next();
w = sc.nextInt();
int i = locateVex(g, u);
int j = locateVex(g, v);
if (i != -1 && j != -1) {
g.Edge[i][j] = w;
} else {
g.edgenum++;
}
}
}
private static void init(AMGraph g, int u) {
for (int i = 0; i < g.vexnum; i++) {
dist[i] = g.Edge[u][i];
flag[i] = false;
if (dist[i] == INF) {
p[i] = -1;
} else {
p[i] = u;
}
}
}
private static void dijkstra(AMGraph g, String x) {
int u = locateVex(g, x);
init(g, u);
dist[u] = 0;
flag[u] = true;
for (int i = 0; i < g.vexnum; i++) {
int temp = INF;
int t = u;
for (int j = 0; j < g.vexnum; j++) {
if (!flag[j] && dist[j] < temp) {
temp = dist[j];
t = j;
}
}
if (t == u) {
return;
}
flag[t] = true;
for (int j = 0; j < g.vexnum; j++) {
if (!flag[j] && g.Edge[t][j] < INF) {
if (dist[j] > dist[t] + g.Edge[t][j]) {
dist[j] = dist[t] + g.Edge[t][j];
p[j] = t;
}
}
}
}
}
public static void findPath(AMGraph g, String u) {
LinkedList<Integer> s = new LinkedList<>();
int x;
for (int i = 0; i < g.vexnum; i++) {
x = p[i];
if (x == -1 && !u.equals(g.Vex[i])) {
System.out.println(u + "无法到达" + g.Vex[i]);
continue;
}
while (x != -1) {
s.push(x);
x = p[x];
}
System.out.print(u + "到" + g.Vex[i] + "的路径为:");
while (!s.isEmpty()) {
System.out.print(g.Vex[s.pop()] + "---");
}
System.out.println(g.Vex[i] + " 距离为:" + dist[i]);
}
}
public static void main(String[] args) {
AMGraph g = new AMGraph();
String u;
createAMGraph(g);
u = sc.next();
dijkstra(g, u);
findPath(g, u);
}
}
/*
5 8
1 2 3 4 5
1 2 2
1 3 5
1 4 2
2 5 3
4 1 7
4 5 6
4 2 1
4 3 3
1
*/
在 Dijkstra 算法描述中,一共有 4 个 for 语句,第一个 for 语句(init)执行次数为 n,第二个 for 语句里面嵌套了两个 for 语句,它们的执行次数均为 n,对算法的运行时间贡献最大。
当外层循环标号为 1 时,第三四语句在内层循环的控制下均执行 n 次,外层循环从 1~n。
因此,该语句的执行次数为
n
×
n
=
n
2
n×n = n^2
n×n=n2,算法的 时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
由以上算法可以得出,实现该算法所需要的辅助空间包括为数组 flag,变量 i、j、t 和 temp 所分配的空间。
因此 空间复杂度为
O
(
n
)
O(n)
O(n)。
第三个 for 语句是在集合 V-S 中寻找距离源点 u 最近的顶点 t,如果穷举需要时间 O ( n ) O(n) O(n),第三个 for 语句的世家复杂度为 O ( n 2 ) O(n^2) O(n2) 。
如果使用优先队列,则找一个最近顶点需要
O
(
l
o
g
n
)
O(logn)
O(logn) 时间,第三个 for 语句的 时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
c++代码如下(示例):
#include
#include
#include "queue"
using namespace std;
const int N = 1e3;
const int INF = 0x3f3f3f3f;
int n, m, cnt;
int head[N], dist[N];
bool flag[N];
typedef pair<int, int> P;
priority_queue<P, vector<P>, greater<> > que;
struct Edge {
int to;
int next;
int w;
} e[N];
void Init() {
memset(head, -1, sizeof head);
memset(dist, INF, sizeof dist);
memset(flag, false, sizeof flag);
cnt = 0;
}
void add(int u, int v, int w) {
e[cnt].to = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt++;
}
void Dijkstra(int u) {
dist[u] = 0;
que.push(P(0, u));
while (!que.empty()) {
P x = que.top();
que.pop();
int t = x.second;
if (flag[t]) {
continue;
}
flag[t] = true;
for (int i = head[t]; ~i; i = e[i].next) {
int v = e[i].to;
if (dist[v] > dist[t] + e[i].w) {
dist[v] = dist[t] + e[i].w;
que.push(P(dist[v], v));
}
}
}
}
void Print(int u) {
for (int i = 1; i <= n; i++) {
cout << u << "到" << i << "的最短距离为:" << dist[i] << endl;
}
}
int main() {
cin >> n >> m;
Init();
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
add(u, v, w);
}
cin >> u;
Dijkstra(u);
Print(u);
return 0;
}
/*
5 8
1 2 2
1 3 5
1 4 2
2 5 3
4 1 7
4 5 6
4 2 1
4 3 3
1
*/
第四个 for 语句有松弛操作,如果采用邻接矩阵存储图,每次需要执行 n 次,第四个 for 语句 时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
如果采用邻接表存储,则每次执行 t 顶点的度数 x,每个顶点的度数之和为边数 e,第四个 for 语句 时间复杂度为
O
(
e
)
O(e)
O(e)。
对于稀疏图, O ( e ) O(e) O(e) 要比 O ( n 2 ) O(n^2) O(n2) 小。
下期预告:图的最短路径-Bellman_Ford算法