摘要:
描述:单源 最短路径(找到指定顶点到其余各点的最短路径)
思想:greedy
复杂度:
时间复杂度:O(n^2)
空间复杂度:O(n)
思想概要:
关键:每次将V-U内到起点最短的顶点加入到U,并且尝试作为中转点,缩短起点到V-U其他顶点的距离
这样做,下一条构成的路径(设终点为x),必然是v0到vx的最短路径,且必定是b2或者是B1+b2。即,v0到vx的最短路径不会是通过A1 + a2 + A3这种方法产生的
辅助图解:
U表示已找出的,从v0出发的,最短路径的终点;V-U是待找出的
(
1:所有顶点都在U内的路径;
2:连通U和V-U的弧
3:所有顶点都在V-U内的路径;
Vx表示顶点,
a2表示某条连通U和V-U的弧
A1表示连通起点到弧a2的在U里的顶点的路径
A3表示连通弧a2的在V-U里的顶点到终点的路径
)
证明:
简要证明:
按照这个过程,会存在一条比b2/B1+b2算法更短的路径,那就只能是A1 + a2 + A3,而A1 + a2 + A3比b2/B1+b2小意味着a2必须小于b2,但这与b2的语义(b2是当前连通U和V-U最短的弧) 相矛盾
详细证明:(反证法:要证v0到x可能是通过A1 + a2 + A3产生的)
假设v0到x的最短路径的构成是成分是A1 + a2 + A3,则A1 + a2 + A3必然小于b2,且A1 + a2 + A3必然小于任意一条B1+b2。
如果A1 + a2 + A3 < b2,那么A1,a2,A3三个成分至少有一个是小于b2的。
然后你就突然发现,只要允许路径权值和为负值的话,这没有任何毛病,没办法反证!!只有所有弧的权值>=0证明才能成立。
在所有弧的权值都>=0前提下
》对于A1 + a2 + A3 < b2的推论:
A1,a2,A3必须都小于b2,但既然a2<b2,按照约定的算法,下一条被加入的弧应该是a2而不是b2,与算法事实相矛盾
》对于A1 + a2 + A3 < B1 + b2的推论:
如果有A1 + a2 + A3 小于任意一条 B1+b2,就有A1 + a2 < B1+b2,意味着U中任意一个顶点到x的距离都大于a2(即存在另一条弧,比选定的将加入弧更小,因此这次选择是错误的),但按照算法约定,a2才是下一个被加入的弧
我最大的疑惑:
1.最小生成树的任意两点之间的路径是不是连通这两点的最短路径?(这也是Dijkstra跟Prim的区别所在)
答:不是,由下图可知,A{<v0,v1>,<v1,v2>,<v2,v3>}和B{<v0,v1>,<v1,v3>,<v2,v3>}都是最小生成树,但A中从v0到v3路径长6,B中v0到v3路径长4
2.在将顶点从V-U加入到U过程中,是选择v0到V-U最短的路径,还是选择U到V-U最短的路径
答:选择v0到V-U最短的路径,道理同上,如果只考虑U到V-U而不从这条路径考虑,遇到这种<v1,v2>和<v0,v3>弧相等的情况就没办法准确的抉择
3.(假设在一个连通图里)按迪杰斯特拉方法将某点到其余所有顶点的最短路径都建立完后,是不是一棵最小生成树
(假设在一个无向连通图里)首先最短路径都建立完后,肯定是一个连通图,因为所有点都和v0连通,而连通是可传递的
其次,它肯定是一棵生成树,否则它必定有环(连通但不是极小意味着有环)但这跟“只要顶点被加入U,v0到它的最短路径就已经被找到”的结论不符合
然后它也是最小生成树。假设它不是MST,
解释1:那么在v0到达某点vx之间必定存在另一条更短的路径,但这是不可能大,正如上面说,如果这另一条是v0直达vx的,就一定是“选V-U中v0到它路径最短的点”的结果;如果是其他点v间接到达的,因为v0到v肯定比v0到vx更快,所以v必然在vx之前就加入到U了
解释2:那么某点vx被加入U时选择的必然是另一条更短的路径,且并非B1+b2或b2产生的结果;
那就只能是A1+a2+A3这种方法的结果了,但这样被a2连通的节点必定先于vx被加入U,也就是说,实际上会演变成B1+b2的情况
=====================================================================
实现:
注意,迪杰斯特拉的完整过程,是找到一个起点到其余所有顶点的最短路径,如果只希望找到指定终点的最短路径,最糟糕的情况下,需要先找到前面所有顶点的最短路径
其实,Dijkstra和Prim的思想十分类似,都是贪心算法的思想,我自己想的时候,都是先将需要的数据准备好。但这两种方法的精妙之处,就在于需要的数据是一边更新状态一边获取的。
#define INF INT_MAX
struct Edge
{
int b, e, w;
Edge() :b(-1), e(-1), w(-1) {}
Edge(int begin, int end, int weight) :b(begin), e(end), w(weight) {}
};
struct AdjMatrix
{
int vn, en;
vector<vector<int>> m;
AdjMatrix(const vector<vector<int>>& mat)
{
m = mat;
vn = mat.size();
}
};
list<Edge> Dijkstra(const AdjMatrix& mat, int src, int dst)
{
if (mat.vn <= 0) return list<Edge>();
if (src == dst) return list<Edge>{Edge(src,src,0)};
int n = mat.vn;
list<Edge> path;
vector<bool> vs(n, false);
vector<int> prev(n, src);
vector<int> minArc(n, INF);
minArc[src] = 0;
vs[src] = true;
prev[src] = -1;
int k = 1;
int v = src;
while (k <= n - 1)
{
int minw = INT_MAX;
int nextv = -1;
for (int i = 0; i < n; ++i)
{
if (vs[i]) continue;
if ((mat.m[v][i] != INF) && (minArc[v] + mat.m[v][i] < minArc[i])) // 前提是可达
{
minArc[i] = minArc[v] + mat.m[v][i];
prev[i] = v;
}
if (minArc[i] < minw)
{
minw = minArc[i];
nextv = i;
}
}
if (nextv == -1) break;
v = nextv;
vs[v] = true;
if (v == dst) break;
++k;
}
if (v == dst) // 回溯构建路径
{
int pv, cur = dst;
do {
pv = prev[cur];
path.push_front(Edge(pv, cur, mat.m[pv][cur]));
cur = pv;
} while (pv != src);
}
return path;
}
TestCase:
int main()
{
vector<vector<int>> m{
{INF,4,INF,INF,2},
{4,INF,INF,INF,INF},
{INF,INF,INF,1,3},
{INF,INF,1,INF,INF},
{2,INF,3,INF,INF},
};
AdjMatrix mat(m);
auto l = Floyd(mat);
getchar();
return 0;
}
参考
【1】https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-using-priority_queue-stl/
【2】https://www.geeksforgeeks.org/widest-path-problem-practical-application-of-dijkstras-algorithm/