最短路径:dijkstra算法

设用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<vi,vj>上的权值,若<vi,vj>不存在,则arcs[i][j]=无穷大。

设S为已找到从v出发的最短路径集合,它的初始状态为空集,设T=V-S为图中剩余顶点集合,编程时可以使用长度为n(n为图的顶点数目)数组visited表示集合S,visited[i]=1表示第i个顶点属于集合S,visited[i]=0表示第i个顶点属于集合T。设置辅助数组D[n],D[i]表示从源点v0到顶点i的最短路径长度

Dijkstra算法是最经典的最短路算法,用于计算正权图的单源最短路(Single Source Shortest Path,源点给定,通过该算法可以求出起点到所有点的最短路),它是基于这样一个事实:如果源点到k点的最短路已经求出,并且保存在D[k]上,那么可以利用k去更新k能够直接到达的所有点x的最短路(D[x] = min(D[x],D[k]+map[k][x]) )。

一个非常重要的性质
性质:设S为已求得最短路径的终点的集合,可以证明:下一条最短路径(设其终点为x)或者是弧<v0, x>,或者是中间只经过S中的顶点而最后到达终点x的路径。该性质的含义为下一条最短路径或者是从源点直接到达终点x,或者是只经过S中的顶点作为中间顶点中转到达终点x

反证法证明(严蔚敏老师的数据结构中的内容,加上自己的理解,如果不对请指出):假设S为已求得最短路径的终点的集合,下一条最短路径为a->b->c-x,其中a,b在集合S中,c,x在集合T中。这说明存在一条终点不在集合S中的路径a->b->c,其长度比路径a->b->c->x短。但是这是不可能的,因为我们是按路径长度递增的次序来产生各最短路径的,故长度比次路径(a->b->c->x)短的所有路径均已产生,他们的终点必定在S中,即假设不成立!

dijkstra算法:
(1)初始化:D[i] = arcs[v0][i], visited[i] = 0,visited[v0] = 1
(2)选择vj,从集合T中选择使得数组D中值最小的顶点vj,D[j] = Min{D[i]|vi属于T},vj就是当前求得的下一条从v0出发的最短路径的终点。将顶点j加入集合S中,令visited[j] = 1
(3)修改从v0出发到集合T上任意一顶点vk可达的最短路径长度,如果D[j] + arcs[j][k] < D[k],则修改D[k] = D[j] + arcs[j][k]
(4)重复(2),(3)共n-1次,由此求得从v0到图上其他顶点的最短路径长度

这里需要解释一下为什么集合T中使得数组D中值最小的顶点vj是下一条最短路径的终点?这里可以分两种情况(下面所说的vj表示下一条最短路径的终点)
①第一次执行步骤(2)时,这时S集合中只有源点v0,因为D[vj]是最小的,即是从v0到vj是最短的路径,因为dijkstra算法只适用于正权图,权值一定是正的,这时如果v0通过其他顶点作为中间顶点再最后到达vj一定会增加权重,所以开始S中只有v0时,vj一定是下一条最短路径的终点(间接说明了为什么dijkstra算法不适用与负权图)。
②如果不是第一次执行步骤(2)时,集合S中有多个顶点。因为该算法的核心为步骤2,3的多次重复,所以当不是第一次执行步骤(2)时,这时必定是执行过步骤3再次回到步骤2,由上面的性质可知,vj一定是从v0直接到达vj或者是S中顶点作为中间顶点再到达vj,而注意在步骤3中,更新数组D时,同时考虑了从v0直接到达各个顶点的长度和从v0经过S中顶点作为中间顶点到达各个顶点的长度,并且取这两种路径较短的路径赋值给D[k]( D[k]=min(D[j]+arcs[j][k], D[k]) )。因为上面所述的性质限定了下一条最短路径的终点只可能有这两种情况产生,而在步骤(3)中已经取了这两种情况的最短路径,所以使得D最小的vj一定是下一条最短路径的终点。

算法实现
图用带权邻接矩阵存储map[][]
数组distance[]存放当前找到的从源点V0到每个终点的最短路径长度,其初态为图中直接路径权值
数组parent[]表示从V0到各终点的最短路径上,此顶点的前一顶点的序号;若从V0到某终点无路径,则用-1作为其前一顶点的序号

#include <climits>
#include <iostream>
#include <stdio.h>
#include <stack>
using namespace std;

const int INFI = 1000; //1000作为最大值,没有使用INT_MAX,因为下面运算可能会造成溢出
int v, m;              //v为图的顶点数,m为图中边的数目

void dijkstra(int v0, int map[][100], int parent[], int distance[], int visited[]) //源顶点从0开始
{
    for (int i = 1; i <= v; i++)
    {
        visited[i] = 0; //初始化所有点都在S集合中
        if (map[v0][i] < INFI)
            parent[i] = v0; //由源点直接到达
        else
            parent[i] = -1;
        distance[i] = map[v0][i];
    }
    visited[v0] = 1; //源点v0加入集合S中
    parent[v0] = -1; //起始点没有父节点,设置为-1
    //以上为初始化工作

    //dijkstra算法核心
    for (int k = 1; k < v; k++) //循环v-1次,v0到其余v-1个顶点的最短路径
    {
        int minDis = INFI; //minDis为以v0出发的最短的路径长度
        int minIndex = -1;
        for (int i = 1; i <= v; i++) //找集合T到集合S最短距离的点
        {
            if (!visited[i] && distance[i] < minDis)
            {
                minIndex = i;
                minDis = distance[i];
            }
        }
        if (minIndex == -1)
            break;
        visited[minIndex] = 1; //将最短路径的点加入集合S中

        for (int i = 1; i <= v; i++) //新的点加入S后,更新到达集合T中点的距离
        {
            if (!visited[i] && distance[i] > minDis + map[minIndex][i]) //更新集合S到集合T的距离
            {
                distance[i] = minDis + map[minIndex][i];
                parent[i] = minIndex; //通过minIndex到达终点i比之前的假设由v0直接到达终点i更短
            }
        }
    }
}

void print_shortestPath(int start, int end, int parent[])
{ //输出从源点start到终点end的最短路径
    stack<int> s;
    if(parent[end]==-1)
    {
        printf("不能从源点%d到底终点%d", start, end);
        return;
    }
        
    s.push(end);
    int k = parent[end];
    while(k!=-1){
        s.push(k);
        k = parent[k];
    }

    printf("从点 %d 到点 %d 的最短路径为:", start, end);
    while(!s.empty()){
        int top = s.top();
        cout << top << "->";
        s.pop();
    }
    cout << endl;
}

int main()
{
    cin >> v >> m;

    int map[100][100];
    int visited[v + 1] = {0}; //标记图上的点是否已经加入最短路径中
    int parent[v + 1];        //记录每个顶点的前驱节点,也就是父节点
    int distance[v + 1];      //存储最短路径长度

    //初始化map
    for (int i = 1; i <= v; i++) //没有使用第0行和第0列
        for (int j = 1; j <= v; j++)
        {
            map[i][j] = INFI;
            map[i][i] = 0;
        }
    for (int i = 0; i < m; i++) //输入m条边
    {
        int a, b, c;
        cin >> a >> b >> c;
        map[a][b] = c;
    }

    dijkstra(1, map, parent, distance, visited);

    cout << "distance数组(源点到各个点的最短路径长度):";
    for (int i = 1; i <= v; i++)
    {
        cout << distance[i] << " ";
    }
    cout << endl;

    cout << "parent数组(最短路上终点的前一个节点):";
    for (int i = 1; i <= v; ++i)
    {
        cout << parent[i] << " ";
    }
    cout << endl;

    for (int i = 1; i <= v;i++){
        print_shortestPath(1, i, parent);
    }
        
    system("pause");
    return 0;
}
/*输入
7 10
1 2 13
1 3 8
1 5 30
1 7 32
2 6 9
2 7 7
3 4 5
4 5 6
5 6 2
6 7 17

*/

输入一下数据描述下图中的图
7 10
1 2 13
1 3 8
1 5 30
1 7 32
2 6 9
2 7 7
3 4 5
4 5 6
5 6 2
6 7 17
最短路径:dijkstra算法

程序运行结果
最短路径:dijkstra算法