Dijkstra算法
在正权有向图寻找源点到其它点的最短路径
一、实现:
定义: “最短路径值”:某个点到源点的最短距离
核心思想:
每新知道某个点的最短路径值,都更新其邻接点的的未知最短路径值(根据邻接边权值)。
将剩下点中未知最短路径值最小的那个点,确定为已知点,其未知最短路径值确定为那个点的最短路径值(即知道了一个新点的最短路径值)。继续更新,继续确定,直到确定了所有点的最短路径值(到源点的最短距离)。
可以想象一个画面:
黑暗的平面上,只有一个源点独自闪光,
其它点都隐藏在虚空之中,与源点毫无瓜葛。
以源点为中心扩散开金色的波动,其它点到源点的距离被更新了,
离源点最近的点从黑暗中现身,也扩散开金色的波动,其它点到源点的距离又被更新了
又一个离源点最近的点从黑暗中现身,又扩散开金色的波动。。。
就这样,所有的点都被激活了,如果被激活的点还记得前世那个点燃它的点,并与它连上一条金色的丝线,
那么,所有的最短路径都浮现在你的眼前,所有的回忆都熠熠闪光,清晰可见
值得注意的是,被唤醒的点不一定是前一个被唤醒点的后继,而可能早有注定之人(更早的唤醒点更新了其到源点的最短路径)。
具体化:
//定义
“最短路径值 Lk”:k点到源点s的最小距离
wij:从i点到j点的有向边的权值
S集:已知最短路径值点的集合
Q集:未知最短路径值点的集合
//初始化
初始化源点s的最短路径值(到自身)为0,其它点的最短路径值为∞,
S集为空集,即没有已知最短路径值
Q集为点集,即所有点都未知最短路径值
//从Q集(未知最短路径点)中选点加入S集(已知最短路径点)
找出Q集中最短路径值最小的点v,放入S集,称v点已知最短路径值
第一次,点v为源点s,Ls=0,L其它=∞
//更新Q集未知最短路径点的最短路径值
以此更新去掉v点后Q集中v点的邻接点q的最短路径值
Lq=min(Lq,Lv+wvq),
即,如果以新选出的已知最短路径点v的最短路径作为源点走到q的前路,能使q的最短路径值变小,就替换掉原来的最短路径值。
第一次,v点为源点s,L其它被更新,L邻接s=邻接边权值,L不邻接s=∞
//重复 选出已知最短路径值的点,更新未知最短路径值点的最短路径值
也就是每次都贪心的确定最小未知最短路径值,作为已知的最短路径值
//如果要记录路径
被更新成功的点记录更新它的桥梁点,一直找桥梁点可以找回源点,被找到的点,就是最短路径。
二、数学证明:
终于见到了一篇令我茅塞顿开的文章:
https://www.cnblogs.com/star-eternal/p/7704532.html
参考链接文章所写的数学证明:
证明按照该算法确定的已知最短路径点,确实是最短路径点。
①n=1
该算法确定的第一个点为源点s,确实是最短路径点。
②n=2
该算法确定的第二个点为源点s最近的一个邻接点,确实是最短路径点。
③假设n=k成立,证明n=k+1成立
即证明,当按照该算法找到的k个点,其Lk确实为最短路径值时,按照该算法找到的第k+1个点q,设其桥梁点是p,它的最短路径值L就是Lq=Lp+wpq。
反证:
假设q点有更小的最短距离值L,使L<Lq=Lp+wpq,
因为大前提已经找到的k个点,其Lk确实为最短路径值,且这些最短路径都是在已知最短路径的基础上延伸出来的,根据所有已知最短路径点更新未知最短路径点的结果,找出L=Lp+wpq = Lq,故经过已知最短路径点到q点,其L = Lq
故实际最短路径L,必经过一未知最短路径点x,才有可能使L<Lq。
此时L=Lx+wxq。由于L>Lx,且x也是未知最短路径点,故按照算法找到的第k+1个点不是q而是x了,与大前提 按照算法找到的第k+1个点为q矛盾。
故q点没有更小的最短距离值,即按照算法找出的Lq=Lp+wpq就是q点的最短距离值。
综上:该算法找到的最短路径点的最短路径值,确实是最短路径值。贪心有理。
三、C语言实现的程序
// 编写软件实现下面功能
// 1. 输入城市个数n,建立n个城市铁路关系及费用
// 2. 求任意2个城市的最小开销并输出路径
#include <stdio.h>
#include <stdlib.h>
#define N 100 //数组最大数目
#define INF 10000 // 距离无穷大,无路
void Dijkstra(int num, int from, int to, double edge[N][N]) //点数,源点,目标点,边关系二维数组
{
double d[N]; //d[i]存储 i点到源点的最短距离
int finded[N]; //finded[i]=1表示已找到i点最短距离,即纳入基本点集
int path[N]; //保存前一个点
//初始化
int i;
for (i = 1; i <= num; i++)
{
d[i] = 10000; //最短距离目前全为 直接到源点的距离
finded[i] = 0; //没有基本点
path[i] = from; //所有点的上一个点为源点
}
d[from] = 0;
//主循环,循环城市个数次
for (i = 1; i <= num; i++)
{
//纳入新的基本点
double min = INF;
int j;
int point;//新的基本点
//找到新的基本点
for (j = 1; j <= num; j++)
{
if (!finded[j] && d[j] < min) //如果不是基本点,且到源点的最短距离最小
{
min = d[j];
point = j;
}
}
finded[point] = 1;//纳入新的基本点
//更新非基本点的最短距离
int k;
for (k = 1; k <= num; k++)
{
if (!finded[k] && (d[point] + edge[point][k] < d[k])) //如果不是基本点,以新基本点为桥梁点看是否要更新最短距离,
{
d[k] = d[point] + edge[point][k]; //该点最短距离更新
path[k] = point; //该点前一个点更新
}
}
}
if (d[to] >= INF)
{
printf("\n这两个城市之间不存在通路!查找失败\n");
return;
}
printf("\n这两个城市之间的最短距离为:%.2lf\n", d[to]);
printf("最短路径为:");
printf("%d<-", to);
int p = to;
while (from != path[p])
{
p = path[p];
printf("%d<-", p);
}
printf("%d\n", from);
}
int main()
{
int Cnumber, from, to; //城市数,源点,目标点
printf("请输入城市个数:");
scanf("%d", &Cnumber);
double edge[N][N];//边关系二维数组
//输入边关系二维数组
int i, j;
for (i = 1; i <= Cnumber; i++) //初始化城市之间的费用
for (j = 1; j <= Cnumber; j++)
{
if (i == j)
{
edge[i][j] = 0;
}
else
{
printf("请输入第%d个城市到第%d个城市的费用,(若两个城市之间没有关联,请输入大于等于10000的数):\n", i, j);
scanf("%lf", &edge[i][j]);
}
}
printf("请输入您想要查询的两城市编号(使用空格隔开):");
scanf("%d %d", &from, &to);
Dijkstra(Cnumber, from, to, edge);
system("pause");
return 0;
}