迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,它用于解决的是有向图中最短路径问题。Dijkstra 算法能够计算出给定起**【起始顶点】** 到【其他所有顶点】的最短路径。
- 算法过程 {#title-0} ==================
我们通过一个例子来分析下 Dijkstra 算法是如何找到最短路径的,首先我们初始化 3 个数组:
- 用于记录两顶点之间距离的一维数组 source_others(如果两个顶点没有边则用 M 表示之间的距离,M 是一个较大的数字,我们这里用 65535 表示);
- 用于记录两顶点是否已经找到最短路径的一维 bool 数组 shortest_done(初始化时,自身到自身设置为 true,表示已经找到最短路径,其余都设置为 false);
- 一个用于记录最短路径的一维数组 shortest_path(该矩阵用于记录任意两个顶点经过 k 顶点距离最短,如果 k 不存在,则记录为起始顶点,表示不需要经过任何顶点距离已经是最短,例如:1顶点和6顶点之间,发现经过3顶点距离更短,则该矩阵 [1][6] 位置记录为 3。1顶点和2顶点之间,发现直接距离更短,则[1][2]位置记录为起始顶点1)。
假设:我们的起始顶点为 1 顶点,Dijkstra 算法要计算 1 顶点其他所有顶点的最短路径,则:
初始化:
- source_others = [0, 7, 9, M, M, 14]
- shortest_done = [true, false, false, false, false, false]
- shortest_path = [1, 1, 1, 1, 1, 1]
1.1 第 1 次迭代 {#title-1}
- 搜索距离 1 顶点最近的顶点是谁?并且记录与其距离。
- 1->1 已标记最短路径,跳过;
- 1->2 距离为 7;
- 1->3 距离为 9;
- 1->4 距离为 M;
- 1->5 距离为 M;
- 1->6 距离为 14;
- 可知,1->2 的距离为 7, 距离最短。并将 shortest_done[1][2] 标记为 true, 表示 1 顶点到 2 顶点的最短路径找到。
- 接下来计算 1 顶点经过 2 顶点到其他顶点的距离是否比 1 直接到其他顶点更短。
- 1->1 和 1->2->1,由于 1->1 的 shortest_done 标记为 true, 跳过;
- 1->2 和 1->2->2,由于 1->2 的 shortest_done 标记为 true, 跳过;
- 1->3 和 1->2->3,1->3 的距离为 9,1->2->3 = 7+10=17, 距离更远,跳过;
- 1->4 和 1->2->4,1->4 的距离为 M, 1->2->4 = 7+15=22,距离更近,则在 source_others 中将 1->4 的距离从 M 更新成 22, 并在 shortest_path 中记录 1->4 需要经过 2 距离最短,即:shortest_path[1][4] = 2;
- 1->5 和 1->2->5,1->5 的距离为 M,1->2->5 = 7+M > M,跳过。
- 1->6 和 1->2->6,1->6 的距离为 14,1->2->6 = 7+M > 14,跳过。
得到:
shortest_done = [true, true , false, false, false, false]
source_others = [0, 7, 9, 22 , M, 14]
shortest_path = [1, 1, 1, 2, 1, 1]
1.2 第 2 次迭代 {#title-2}
- 接下来,从剩余顶点中搜索没有找到最短路径的顶点,并从这些顶点中找出距离最短的顶点:
- 1->2 已标记,跳过;
- 1->3 未标记,距离为 9;
- 1->6 未标记,距离为 14;
- 可知,1->3 距离最短,并将 shortest_done[1][3] 标记为 true, 表示 1 顶点到 3 顶点的最短路径找到。
- 接下来计算起始 1 顶点经过 3 顶点到达其他所有顶点的距离是否比 1 直接到达其他顶点的距离更短。
- 1->1 和 1->3->1,shortest_done[1][1] 已标记找到最短路径,跳过;
- 1->2 和 1->3->2,**shortest_done[1][2]**已标记找到最短路径,跳过;
- 1->3 和 1->3->3,shortest_done[1][3] 已标记找到最短路径,跳过;
- 1->4 和 1->3->4,1->4 的距离为 22,1->3->4 = 9+11=20 < 22,距离更近,则在 source_others 中将 1->4 的距离修改为 20,并在 shortest_path 中记录 1->4 需要经过 3 距离最短,即: shortest_path[1][4] = 3,注意:该值原来是 2,现在更新成 3;
- 1->5 和 1->3->5,1->5 的距离 M,1->3->5 = 9+M > M,跳过;
- 1->6 和 1->3->6,1->6 的距离 14,1->3->6 = 9+2 = 11 < 14, 距离更近,则在 source_others [1][6] 记录矩阵为 11,并在 shortest_path [1][6] = 3.
得到:
shortest_done = [true, true, true , false, false, false]
source_others = [0, 7, 9, 20 , M, 11 ]
shortest_path = [1, 1, 1, 3, 1, 3]
1.3 第 3 次迭代 {#title-3}
- 接着从剩下未在 shortest_done 中标记的顶点中找到距离起始顶点最近的顶点:
- 1->1, 已标记,跳过;
- 1->2, 已标记,跳过;
- 1->3, 已标记,跳过;
- 1->4,距离为 20
- 1->5,距离为 M
- 1->6,距离为 11
- 可知,1->6 距离最短,将其标记为已找到最短路径,并在 shortest_done[1][6] 标记为 true。
- 接下来计算起始 1 顶点经过 6 顶点到达其他顶点的距离是否比直接到达更短:
- 1->1 和 1->6->1, 已标记,跳过;
- 1->2 和 1->6->2, 已标记,跳过;
- 1->3 和 1->6->3, 已标记,跳过;
- 1->4 和 1->6->4,1->4=20,1->6->4=14+M, 距离更大,跳过;
- 1->5 和 1->6->5,1->5=M,1->6->5=11+9=20, 距离更近,则在 source_others [1][5] 中记录距离为 23,并记录 shortest_path[1][5]=6;
- 1->6 和 1->6->6, 已标记,跳过;
得到:
shortest_done = [true, true, true, false, false, true ]
source_others = [0, 7, 9, 20 , 20, 11 ]
shortest_path = [1, 1, 1, 3, 6, 3]
1.4 第 4 次迭代 {#title-4}
- 重复前面的过程,从剩余顶点找到距离起始顶点最近的顶点:
- 1->1, 已标记找到最短路径,跳过;
- 1->2, 已标记找到最短路径,跳过;
- 1->3, 已标记找到最短路径,跳过;
- 1->4,距离为 20;
- 1->5,距离为 20;
- 1->6, 已标记找到最短路径,跳过;
- 可知,1->4 距离最短,并将 **shortest_done[1][**4] 标记为 true.
- 接下来,计算起始顶点经过 4 顶点到其他顶点的距离是否更近:
- 1->1 和 1->4->1, 已标记,跳过;
- 1->2 和 1->4->2, 已标记,跳过;
- 1->3 和 1->4->3, 已标记,跳过;
- 1->4 和 1->4->4, 已标记,跳过;
- 1->5 和 1->4->5,1->5=20,1->4->5=20+6, 距离更远,跳过;
- 1->6 和 1->6->6, 已标记,跳过;
得到:
shortest_done = [true, true, true, true, false, true ]
source_others = [0, 7, 9, 20 , 20, 11 ]
shortest_path = [1, 1, 1, 3, 6, 3]
1.5 第 5 次迭代 {#title-5}
- 继续重复前面的过程,从剩余顶点中找到距离顶点最近的顶点:
- 1->1, 已标记找到最短路径,跳过;
- 1->2, 已标记找到最短路径,跳过;
- 1->3, 已标记找到最短路径,跳过;
- 1->4, 已标记找到最短路径,跳过;
- 1->5,距离为 23;
- 1->6, 已标记找到最短路径,跳过;
- 可知,1->5 距离最短,并将 **shortest_done[1][**5] 标记为 true.
- 此时,所有的顶点都已标记找到最短路径。
得到:
shortest_done = [true, true, true, true, true, true ]
source_others = [0, 7, 9, 20 , 20, 11 ]
shortest_path = [1, 1, 1, 3, 6, 3]
- 算法代码 {#title-6} ==================
#include <iostream>
#include <list>
using namespace std;
#define M 65535
// 定义顶点
int vertex_size = 6;
int vertex[] = { 1, 2, 3, 4, 5, 6 };
// 定义边
int edges[][6] = {
{ 0, 7, 9, M, M, 14},
{ 7, 0, 10, 15, M, M},
{ 9, 10, 0, 11, M, 2},
{ M, 15, 11, 0, 6, M},
{ M, M, M, 6, 0, 9},
{14, M, 2, M, 9, 0}
};
list<list<int>> dijkstra(int source_vertex)
{
// 用于记录起始点到其他各点的距离
int *source_others = new int[vertex_size];
// 用于记录起始点到其他点最短路径
int *shortest_path = new int[vertex_size];
// 用于记录是否已经找到最短路径
bool *shortest_done = new bool[vertex_size];
// 初始化辅助数组
for (int i = 0; i < vertex_size; ++i)
{
source_others[i] = edges[source_vertex][i];
shortest_path[i] = source_vertex;
shortest_done[i] = false;
}
// 将起始节点到起始节点路径标记已经找到,即:忽略计算
shortest_done[source_vertex] = true;
// 开始计算起始顶点到其他顶点的最短路径
for (int i = 0; i < vertex_size; ++i)
{
// 假设到起始到起始为最短距离的顶点
int nearest_vertex = source_vertex;
// 假设 M 为最大距离
int nearest_weight = M;
// 经过 for 循环会找到距离最近的顶点,并获得其距离
for (int j = 0; j < vertex_size; ++j)
{
if (!shortest_done[j] && (source_others[j] < nearest_weight))
{
nearest_vertex = j;
nearest_weight = source_others[j];
}
}
shortest_done[nearest_vertex] = true;
// 更新经过该顶点到其他顶点的最短距离
for (int k = 0; k < vertex_size; ++k)
{
if (!shortest_done[k] && (nearest_weight + edges[nearest_vertex][k] < source_others[k]))
{
// 起始点到 k 顶点的最短距离
source_others[k] = nearest_weight + edges[nearest_vertex][k];
// 记录起始点到 k 顶点经过那个顶点
shortest_path[k] = nearest_vertex;
}
}
}
// 输出起始点到各个顶点的最短路径
list<list<int>> results;
for (int i = 0; i < vertex_size; ++i)
{
list<int> path;
// 回溯路径
int back_vertex = i;
path.push_front(back_vertex + 1);
while (true)
{
back_vertex = shortest_path[back_vertex];
path.push_front(back_vertex + 1);
if (back_vertex == source_vertex) break;
}
results.push_back(path);
}
delete[] source_others;
delete[] shortest_path;
delete[] shortest_done;
return results;
}
int main()
{
auto paths = dijkstra(0); // 起始为 1 顶点
for (auto &path : paths)
{
for (auto v : path)
{
cout << v << " ";
}
cout << endl;
}
return 0;
}
程序输出结果:
1 1
1 2
1 3
1 3 4
1 3 6 5
1 3 6
我们打印下这三个辅助数组的值:
source_others:
0 7 9 20 20 11
shortest_path:
0 0 0 2 5 2
shortest_done:
true true true true true true
这个结果和我们前面手算的结果是一致的。