概念:
求图上一点到另一点的最短距离。
算法:
一:Floyd(弗洛伊德)
Floyd算法可以求出图上任意一点到另一点的最短距离。
思想:通过三重循环,用两重循环来枚举任意两点,一重循环枚举中节点,如果第一个点到中点+另一点到中点的距离<这两点先前的距离,则这两点的距离=第一个点到中点+另一点到中点的距离。
代码如下:
inline void Floyd() {
for (int k=1;k<=n;k++) //枚举中结点,i、j表示另两点
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a[i][k]+a[k][j]<a[i][j]) //如果第一个点到中点+另一点到中点的距离<这两点先前的距离
a[i][j]=a[i][k]+a[k][j]; //更新两点间的距离
}
但是!Floyd的时间复杂度非常高,也非常稳定,为O(n^3)。只能在n<=500的情况下使用。
Floyd可以无视负权,但不能判断负权回路,这就是接下来介绍的Dijkstra算法所不能及的了。
二:Dijkstra(迪杰斯特拉)
Dijkstra算法是求单元点到图中除单元点外任何一点的最短距离。
这个算法只能用来求一个点到其他点的距离,时间复杂度为O(n^2)。如果将每个点都做一遍Dijkstra,那它的效果也就相当于是Floyd。但是它相对于Floyd的好处就是可以O(n^2)求单元点到其他点的距离,这个Floyd就做不到了。
思想:利用贪心的思想,求出在该点连接的边中,找到权值最小的一条,那么这条边就是到这个点到原点的最短路(因为不可能有其他边更新的边会比这条边短了)。再通过这个点,去更新其他与改点所连的点,如果原点到被该点连接的点的距离<原点到改点+改点连接的边的距离,则更新被连点到原点的距离,最后将该点赋值为真,避免下次再重复更新(每个点最初到原点的距离为正无穷,原点到原点除外)。
代码如下://这里采用邻接矩阵的写法,有兴趣可以去参考其他用邻接表的写法
inline void dijkstra(int st) {
memset(f,0,sizeof(f)); //fi表示单元点到i的最短距离是否被确定
memset(dis,10,sizeof(dis)); //memset 10表示正无穷
for (int i=1;i<=n;i++) dis[i]=a[i][st]; //dis数组表示单元点到i的距离
f[st]=1,dis[st]=0; //前者表示单元点到单元点的距离唯一,后者表示单元点到单元点的距离为0
for (int i=1;i<=n;i++) {
int minn=2147483647,k=0; //k表示为未被使用过的连接原点的点的编号(有点拗口)
for (int j=1;j<=n;j++)
if (!f[j] && dis[j]<minn) minn=dis[j],k=j; //!f[j]表示未被使用过
if (k==0) return ; //连接的点中全被使用过
f[k]=1; //标记为已被使用过
for (int j=1;j<=n;j++)
if (!f[j] && dis[k]+a[k][j]<dis[j]) dis[j]=dis[k]+a[k][j]; //三角形迭代,更新最短距离
}
}
这里要特别注意一点就是赋值成正无穷,不仅dis数组,a数组读进来之前也要,不然就会输出0。
Dijkstra算法还可以通过堆优化,可以将时间复杂度优化到O(n log(n)),有兴趣可以去查阅资料,这里就不再说明(主要是因为我也不会)。
但是Dijkstra在有负权回路的情况下,会怎么样,自行想象。在这种情况下,就要用到Bellman-Ford或者SPFA,具体用法见下文。