迪杰斯特拉算法(Dijkstra's Algorithm)是一种用于求解单源最短路径问题的经典算法。它适用于加权图(无负权边的图),能够计算从起始顶点到图中所有其他顶点的最短路径。该算法的核心思想是:使用贪心策略,通过逐步扩展最短路径,直到所有节点都被处理, 最终得到从起始顶点到所有其他顶点的最短路径。
迪杰斯特拉算法的基本原理
1.初始化:
- 设定一个源点(起始点)S。
- 为每个顶点设置一个“距离”值,表示从源点到该顶点的最短已知距离。初始时,源点的距离设为 0,其余顶点的距离设为正无穷大(表示尚未被访问)。
- 设定一个集合,用来存储已经确定了最短路径的顶点,称为“已访问”集合。起始时,这个集合为空。
2.选择最小距离的顶点:
- 在未访问的顶点中,选择一个距离源点最小的顶点,称为“当前顶点”。
- 将这个顶点加入已访问集合。
3.更新邻居顶点的距离:
- 对于当前顶点的所有邻接顶点,计算其通过当前顶点到源点的距离,即通过当前顶点的路径是否比之前已知的最短路径更短。
- 如果更短,则更新该顶点的距离值。
4.重复过程:
- 重复选择距离最小的未访问顶点,更新其邻接顶点的距离,直到所有顶点都被访问,或者所有可达顶点的最短路径都被找到。
详细步骤
假设图中顶点集合为 V,边的权重集合为 E,源点为 S。
1.初始化:
- 设置两个数组:
- dist[]:记录从源点 S 到每个顶点的最短路径长度。初始时,dist[S]=0,其他顶点 dist[i]=∞(正无穷大)。
- visited[]:记录每个顶点是否已确定最短路径,初始时所有顶点都未访问(即 visited[i]=false)。
2.选择当前顶点:
- 在未访问的顶点中,选择距离源点 S最小的顶点 u,即 dist[u] 最小的顶点。
3.标记为已访问:
- 将顶点 u 标记为已访问,即 visited[u]=true。
4.更新邻接定点的最短路径:
- 对顶点 u 的所有邻接顶点 v,如果 v 未访问,且通过顶点 u 到顶点 v 的路径比原先的 dist[v] 短,则更新 dist[v]。
dist[v]=min(dist[v],dist[u]+weight(u,v))
其中,weight(u,v) 是从顶点 u 到 v 的边的权重。
5.重复步骤2到4:
- 不断选择未访问的顶点,直到所有顶点都访问过,或者所有可达顶点的最短路径都确定。
伪代码
Dijkstra(Graph, source):
初始化距离数组 dist[] 和访问标记数组 visited[]
dist[source] = 0
对于每个顶点 v ≠ source: dist[v] = ∞
while 存在未访问的顶点:
u = 未访问顶点中 dist[] 值最小的顶点
将 u 标记为已访问
对于每个 u 的邻接顶点 v:
如果 v 未访问且 dist[u] + weight(u, v) < dist[v]:
更新 dist[v] = dist[u] + weight(u, v)
返回 dist[]
示例
假设有一个图,如下所示:
A --1--> B --2--> C
\ |
4 1
\ |
D --5--> E
- 从顶点 A 出发:
-
- 初始化:
dist[A]=0, dist[B]=∞, dist[C]=∞, dist[D]=∞, dist[E]=∞
- 选择 A,更新 B 和 D 的距离:
dist[B]=1, dist[D]=4
- 选择 B,更新 C 和 E 的距离:
dist[C]=3, dist[E]=2
- 选择 E,无需更新。
- 选择 C,无需更新。
- 选择 D,无需更新。
- 初始化:
最终结果:dist[A]=0, dist[B]=1, dist[C]=3, dist[D]=4, dist[E]=2
。
时间复杂度
- 使用优先队列(最小堆)优化后,时间复杂度为 O((V+E)logV),其中 V 是顶点数,E 是边数。这是因为每次选出最小距离的顶点和更新邻接顶点的操作都可以通过优先队列有效完成。
适用场景
迪杰斯特拉算法适用于边权为非负的加权图,广泛应用于路由优化、网络最短路径计算等场景。
以下是迪杰斯特拉算法的C语言实现代码。此代码实现了从一个源顶点出发,计算到图中其他所有顶点的最短路径。
#include <stdio.h>
#include <limits.h>
#define V 5 // 图中顶点的数量
// 找到距离集合中距离最短的顶点
int minDistance(int dist[], int visited[]) {
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (visited[v] == 0 && dist[v] <= min)
min = dist[v], min_index = v;
return min_index;
}
// 打印从源顶点到其他所有顶点的最短距离
void printSolution(int dist[]) {
printf("Vertex \t Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}
// 迪杰斯特拉算法实现
void dijkstra(int graph[V][V], int src) {
int dist[V]; // 存储从源到每个顶点的最短距离
int visited[V]; // 标记顶点是否被处理
// 初始化距离数组和访问数组
for (int i = 0; i < V; i++) {
dist[i] = INT_MAX;
visited[i] = 0;
}
// 源顶点的距离设为0
dist[src] = 0;
// 找到从源到所有顶点的最短路径
for (int count = 0; count < V - 1; count++) {
int u = minDistance(dist, visited);
// 将选中的顶点标记为已访问
visited[u] = 1;
// 更新该顶点的所有邻接顶点的距离
for (int v = 0; v < V; v++) {
// 如果未访问,且从 u 到 v 存在边,并且从源点经过 u 到 v 的路径比当前 dist[v] 短,则更新 dist[v]
if (!visited[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}
}
// 打印最终的最短路径
printSolution(dist);
}
int main() {
// 图的邻接矩阵表示法
int graph[V][V] = {
{0, 10, 0, 0, 5},
{0, 0, 1, 0, 2},
{0, 0, 0, 4, 0},
{7, 0, 6, 0, 0},
{0, 3, 9, 2, 0}
};
// 从源点 0 开始计算最短路径
dijkstra(graph, 0);
return 0;
}
代码说明:
minDistance()
:
-
- 该函数用于找到未处理顶点中距离源顶点最近的顶点。它通过遍历
dist[]
数组,找到未被访问的、距离最小的顶点。
- 该函数用于找到未处理顶点中距离源顶点最近的顶点。它通过遍历
printSolution()
:
-
- 该函数打印从源点到所有其他顶点的最短距离。
dijkstra()
:
-
- 该函数实现了迪杰斯特拉算法,参数
graph[V][V]
表示图的邻接矩阵,src
是源顶点。 dist[]
数组保存从源点到各顶点的最短路径。visited[]
数组用于记录哪些顶点已经处理过。
- 该函数实现了迪杰斯特拉算法,参数
- 邻接矩阵:
-
- 图是通过邻接矩阵表示的,
graph[i][j]
表示从顶点i
到顶点j
的边的权重。若没有边,则对应元素为 0。
- 图是通过邻接矩阵表示的,
示例图:
text
复制代码
(0)---10---(1)
| / | \
5| 2 1 3
| / \
(4)---3---(2)---(3)
9 4
在这个例子中,程序会计算从顶点 0
出发到其他所有顶点的最短路径,并打印输出。
输出结果:
csharp
复制代码
Vertex Distance from Source
0 0
1 8
2 9
3 7
4 5
- 顶点 0 到其他各个顶点的最短距离分别为:0 到 0 是 0,0 到 1 是 8,0 到 2 是 9,0 到 3 是 7,0 到 4 是 5。
关键点:
- 图的表示:采用邻接矩阵表示法,边的权重保存在矩阵中。
- 时间复杂度:代码中,
minDistance()
函数每次选择未访问顶点中最小距离的顶点,因此算法的时间复杂度为 O(V2)。