基本原理
Dijkstra算法是根据贪心算法实现的,首先找出当前点到所有能到达的点之间最短的距离,然后松弛一次继续循环。所谓松弛一次,就是在已经访问过的点中遍历一遍,看看有没有更近的,如果有更近的就更新距离。这样每次找最近的可达点+松弛遍历历史节点的操作,一直重复就能找到最短路径。
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。
算法步骤
- 指定起始点s。
- 找到起始点相邻的点开始记录
- 将第二步记录的点作为起始点,如果第二部记录了多个点的话,那么按照与起始点s的距离或者权值,其中权值最小的优先依次作为起始点。
- 直到遍历完图中所有的点。
过程图解
known表示是否已知,dv表示临时距离,这个距离实际上是使用已知定点作为中间顶点从s到v的最短路径长,pv记录一起dv变换的最后的顶点
初始配置表
v | Known | dv | pv |
---|---|---|---|
v1 | 0 | 0 | 0 |
v2 | 0 | ∞ | 0 |
v3 | 0 | ∞ | 0 |
v4 | 0 | ∞ | 0 |
v5 | 0 | ∞ | 0 |
v6 | 0 | ∞ | 0 |
v7 | 0 | ∞ | 0 |
在v1被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 0 | 2 | v1 |
v3 | 0 | ∞ | 0 |
v4 | 0 | 1 | v1 |
v5 | 0 | ∞ | 0 |
v6 | 0 | ∞ | 0 |
v7 | 0 | ∞ | 0 |
在v4被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 0 | 2 | v1 |
v3 | 0 | 3 | v4 |
v4 | 1 | 1 | v1 |
v5 | 0 | 3 | v4 |
v6 | 0 | 9 | v4 |
v7 | 0 | 5 | v4 |
在v2被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 1 | 2 | v1 |
v3 | 0 | 3 | v4 |
v4 | 1 | 1 | v1 |
v5 | 0 | 3 | v4 |
v6 | 0 | 9 | v4 |
v7 | 0 | 5 | v4 |
在v5然后v3被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 1 | 2 | v1 |
v3 | 1 | 3 | v4 |
v4 | 1 | 1 | v1 |
v5 | 1 | 3 | v4 |
v6 | 0 | 8 | v3 |
v7 | 0 | 5 | v4 |
在v7被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 1 | 2 | v1 |
v3 | 1 | 3 | v4 |
v4 | 1 | 1 | v1 |
v5 | 1 | 3 | v4 |
v6 | 0 | 6 | v7 |
v7 | 1 | 5 | v4 |
在v6被声明为已知后的表
v | Known | dv | pv |
---|---|---|---|
v1 | 1 | 0 | 0 |
v2 | 1 | 2 | v1 |
v3 | 1 | 3 | v4 |
v4 | 1 | 1 | v1 |
v5 | 1 | 3 | v4 |
v6 | 1 | 6 | v7 |
v7 | 1 | 5 | v4 |
至此我们可以得出从v1出发到所有点的最短路径
伪代码
Dijkstar算法的声明
typeof int Vertex;
struct TableEntry
{
List Header; /*Adjacency list*/
int Known;
DistType Dist;
Vertex Path;
}
/*Vertices are numbered from 0*/
#define NotAVertex(-1)
typeof struct TableEntry Table[NumVertex];
表初始化例程
void InitTable(Vertex Start, Graph G, Table T)
{
int i;
ReadGraph(G,T);
for(i=0; i<NumVertex; i++)
{
T[i].Known = false;
T[i].Dist = Infinity;
T[i].Path = NotVertex;
}
T[Start].dist = 0;
}
显示实际最短路径的例程
void PrintPath(Vertex V, Table T)
{
if(T[V].Path != NotVertex)
{
PrintPath(T[V].Path,T);
prithf("to");
}
printf("%v",V) /*%v is pseudocode*/
}
Dijkstra算法
void Dijkstra(Table T)
{
Vertex V, W;
for(; ;)
{
V = smallest unkonwn diatance vertex;
if(V = NotVertex)
{
break;
}
T[V].Known = true;
for each W adjacent to V
{
if(!T[W].Known)
{
if(T[V].Dist + Cvw < T[W].Dist)
{
Decrease(T[W].Dist to T[V].Dist + Cvw);
T[W].Path = V;
}
}
}
}
}
例题
有 N 个网络节点,标记为 1 到 N。 给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v,w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。 现在,我们从某个节点 K发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。
输入:times =[[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2
输出:2
算法思路
将所有节点分成两类:已确定从起点到当前点的最短路长度的节点,以及未确定从起点到当前点的最短路长度的节点(下面简称「未确定节点」和「已确定节点」)。
每次从「未确定节点」中取一个与起点距离最短的点,将它归类为「已确定节点」,并用它「更新」从起点到其他所有「未确定节点」的距离。直到所有点都被归类为「已确定节点」。
用节点 A「更新」节点 B 的意思是,用起点到节点 A 的最短路长度加上从节点 A 到节点 B 的边的长度,去比较起点到节点 B 的最短路长度,如果前者小于后者,就用前者更新后者。这种操作也被叫做「松弛」。
这里暗含的信息是:每次选择「未确定节点」时,起点到它的最短路径的长度可以被确定。
可以这样理解,因为我们已经用了每一个「已确定节点」更新过了当前节点,无需再次更新(因为一个点不能多次到达)。而当前节点已经是所有「未确定节点」中与起点距离最短的点,不可能被其它「未确定节点」更新。所以当前节点可以被归类为「已确定节点」。
class Solution {
public:
int networkDelayTime(vector<vector<int>> ×, int n, int k) {
const int inf = INT_MAX / 2;
vector<vector<int>> g(n, vector<int>(n, inf));
for (auto &t : times) {
int x = t[0] - 1, y = t[1] - 1;
g[x][y] = t[2];
}
vector<int> dist(n, inf);
dist[k - 1] = 0;
vector<int> used(n);
for (int i = 0; i < n; ++i) {
int x = -1;
for (int y = 0; y < n; ++y) {
if (!used[y] && (x == -1 || dist[y] < dist[x])) {
x = y;
}
}
used[x] = true;
for (int y = 0; y < n; ++y) {
dist[y] = min(dist[y], dist[x] + g[x][y]);
}
}
int ans = *max_element(dist.begin(), dist.end());
return ans == inf ? -1 : ans;
}
};