迪杰斯特拉算法详解
简述
迪杰斯特拉算法是一种基于贪心法求有向图或无向图单源最短路的算法,其本质就是把顶点集划分为两部分,已求出最短路径的集合S和未求出最短路径的集合U,U集里面每个点都有一个边权,代表源点通过S集里的点到达U集的那个点的最短路径(注意这里的最短不一定是全局最短),S一开始只有源点,U里面和源点的边权为路径本身,不相邻的边权为inf,通过贪心不断地把U集合里面的顶点加入S,直到求完源点到所有顶点的最短路径。暴力时间复杂度为O(n方),经过堆优化可为O(nlogn)。
思想过程
如何将集合U里的点一个一个加入集合S呢?
我们发现,我们可以确定,U里面顶点的最小边权就是源点到该路径的最短路径。
例如该图:
A和B直接和源点相邻,那么U集中最小边权对应的顶点就是源点到该顶点的最短路径,在此图就是源点到B点的最短路径为2,因为A点的边权5不是最小边权,所以在这一步还不能确定是不是最短路径。那为什么U集里的最小边权就是源点到该点的最短路径呢?我们用反证法,假设最小边权不是源点到该点设为y的最小路径,那么必然存在一个点设为x,使得源点到x的距离加x到y的距离小于U里的最小边权,那么源点到x的距离就要小于这个最小边权,矛盾!所以结论得证!
在加入S集后,该点要对其他点的边权进行松弛,什么意思呢?U集合里面的边权是源点到该顶点的路径距离嘛,一开始只有和源点相邻的边,但随着S集合新点的加入,源点到其他点的距离会被改变,因为S集合有新的点x加入,那么源点到其他的点的距离可能会被更小的源点到x的距离加x到其他点的距离取代,即如上图:B加入S之后,U集里面A的边权就可以更新为 源点-B+B-A,为4,4比5小,故更新。所以每当有点加入S集的时候,都要对U里面的其他点的边权进行松弛。
于是不断地加入,松弛,加入,松弛,直到全部点都在S里面,就能跑出源点S到其余所有点的最短路径了。
算法模拟
因为S集和U集没有相同的部分,所以我们用一个集合加个标记就可以区分两个集合。
以此图为例,vis[i]为1代表i点加入了S集,设起点为D,对应表如下:
•刚开始S集只有D点,初始化D点自己到自己的最短路为0,与D点直接相邻的只有C和E
•现在将dis里最小的边权加到S里->D到C的最短距离为3,同时松弛其他点
•因为C的加入使B和F点可达,松弛后D到B和F的距离为13和9,然后选择边权最小的E加入S集
•E的加入松弛了F点和G点,同理现在将F加入S集
•F的加入松弛了A点,现在最小边权的点是G,将G加入S集
•G的加入并未能松弛其他点,此时将B加入S集
•B点的加入也并未能松弛其余点,最后将A点加入S集
经过上述操作,我们把所有点都加进S集合里了,最后得出的dis[i]就是源点D到i点的最短距离!
算法的正确性证明
U集边权的本质就是源点通过干S集里面的点到达目标点的最短路径长度,证明迪杰斯特拉算法就是要证明U集合里的最小边权就是全局最短路径,所有的操作都是基于这个结论的,最小边权是最短路径,加入S集,因为S集更新了所以U集的边权肯会变,然后重复。
那么这个结论如何证明呢?
设源点为u,现有一点v属于U集且该点权值最小,设为x,我们使用反证法,假设x不是全局最短路径,那必然存在另一点k不属于S集,使得u到k+k到v小于u到v。那么u到k的权值必然小于u到v,那么u到v的权值就不是U集里面最小的了,应该是k才对,矛盾,命题得证!
代码详解
代码实现分为三部,找最短边权点及其权值,将它加入S集,松弛其他点,在找最小边权的地方复杂度最大,所以总复杂度为O(n方)
int m[maxn][maxn],d[maxn],vis[maxn]; void dij(int u,int n){//起点为u,总顶点数为n d[u]=0;//自己到自己就是0 for(int i=1;i<=n;i++){ int tmp=inf,k=1; for(int j=1;j<=n;j++){ if(vis[j]==0&&d[j]<tmp){ k=j; tmp=d[j]; } }//此时tmp就是U集中最小的边权,k就是对应的顶点 vis[k]=1;//k点加入S集 for(int j=1;j<=n;j++){ if(vis[j]==0&&tmp+m[k][j]<d[j]){//因为k的加入,可以松弛U的其他点 d[j]=tmp+m[k][j]; } } } }
模板
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int m[maxn][maxn],d[maxn],vis[maxn]; void dij(int u,int n){ d[u]=0; for(int i=1;i<=n;i++){ int tmp=inf,k=1; for(int j=1;j<=n;j++){ if(vis[j]==0&&d[j]<tmp){ k=j; tmp=d[j]; } } vis[k]=1; for(int j=1;j<=n;j++){ if(vis[j]==0&&tmp+m[k][j]<d[j]){ d[j]=tmp+m[k][j]; } } } }