图的最短路径的Dijkstra算法及Floyd算法

文章目录

    • 最短路径的概念
    • 求单源最短路径的Dijkstra算法
    • 求各个顶点之间最短路径的Floyd算法
    • 完整代码


最短路径的概念

 在一个无权图中,若从一个顶点到另一个顶点存在着一条路径(仅限于无回路的简单路径),则该路径上的边数即为路径长度,等于该路径上的顶点数减一
 从一个顶点到另一个顶点所有可能的路径中,路径长度最短(即经过的边数最少)的路径称为最短路径,其路径长度叫做最短路径长度或最短距离。

 在一个带权图中,从一个顶点v到另一个顶点u的路径上所经过各边上的权值之和即为该路径的带权路径长度,从v到u可能不止一条路径,把带权路径长度最短(即其值最小)的那条路径称作是最短路径,其权值之和称作最短路径长度或最短距离。


求单源最短路径的Dijkstra算法

Dijstra算法用于求图中一个顶点到其余各顶点的最短路径
算法设置

  • 一个集合 S S S,记录已经求的最短路径的顶点;
  • 一个辅助数组 d i s t [ ] dist[] dist[] d i s t [ i ] dist[i] dist[i]存放集合 S S S内顶点到集合 S S S外顶点 i i i的最短距离,即源点到其他各顶点的当前最短路径长度
  • 一个辅助数组 p a t h [ ] path[] path[] p a t h [ i ] path[i] path[i]记录集合 S S S外顶点 i i i距离集合 S S S内哪个顶点最近,即源点到顶点 i i i之间的最短路径的前驱结点,在算法结束时,可以根据其值追溯得到源点到顶点 v i v_i vi的最短路径。

算法步骤:

  • 假设从选择从顶点0出发,即 u u u=0,则S内最初只有一个顶点0, S [ 0 ] S[0] S[0] = 1。
  • 再令 p a t h [ 0 ] path[0] path[0]= -1,其他 p a t h [ i ] path[i] path[i]= 0,表示集合S外各个顶点 i i i距离集合 S S S内顶点0最近。
  • 数组 d i s t [ i ] dist[i] dist[i] = G.Edge [ 0 ] [ i ] [0][i] [0][i],表示源点0(集合内顶点)到顶点 i i i的最短距离。如果 d i s t [ i ] dist[i] dist[i] = m a x w e i g h t maxweight maxweight,则表示没有到顶点 i i i的路径。然后重复以下工作:
    • d i s t [ ] dist[] dist[]中选择满足 S [ i ] S[i] S[i] = 0的 d i s t [ i ] dist[i] dist[i]最小的顶点 i i i,用 v v v标记它。则选中的路径长度最短的边为< p a t h [ v ] , v path[v], v path[v],v>,相应的最短路径为 d i s t [ v ] dist[v] dist[v]
    • S [ v ] S[v] S[v] = 1,表示它已经加入集合 S S S
    • d i s t [ i ] = m i n { d i s t [ i ] , d i s t [ v ] dist[i] = min\{dist[i], dist[v] dist[i]=min{dist[i],dist[v] + G.Edge [ v ] [ i ] } [v][i]\} [v][i]}。即检查集合 S S S外各顶点 i i i,如果绕过顶点v到顶点i的距离 d i s t [ v ] dist[v] dist[v] + G.Edge [ v ] [ i ] [v][i] [v][i]比原来集合 S S S中顶点到顶点 i i i的的最短距离 d i s t [ i ] dist[i] dist[i]还要小,则修改到顶点 i i i的最短距离为 d i s t [ v ] dist[v] dist[v] + G.Edge [ v ] [ i ] [v][i] [v][i],同时修改 p a t h [ i ] path[i] path[i] = v v v,表示集合 S S S内顶到 v v v到集合外顶点 i i i的当前距离最近。

代码实现:

void Dijkatra(MGraph &G, int u, int dist[], int path[]){
    int S[maxvertexnum]; //S为已求最短路径的顶点集合
    for(int i=0; i<G.vexnum; i++)S[i]=0; //集合初始化
    S[u] = 1; //起始点进集合
    for(int i=0; i<G.vexnum; i++){ //dist与path数组初始化
        dist[i] = G.Edge[u][i]; //表示源点u到顶点i的最短距离
        if(u!=i && dist[i]<maxweight) //源点u到i有路径
            path[i] = u; //i的前驱结点为u
        else
            path[i] = -1; //否则i没有前驱结点
    }
    for(int i=0; i<G.vexnum; i++){ //对所有的顶点处理一次
        if(i != u){ //排除源点
            int min = maxweight; //选不属于S且具有最短路径的顶点v
            int v = u;
            for(int j=0; j<G.vexnum; j++){
                if(!S[j] && dist[j]<min){ //找最短路径,就是找dist[i]的最小值
                    v = j; //取最小值的顶点下标
                    min = dist[j]; //最小值
                }
            }
            S[v] = 1; //将顶点v加入集合
            for(int j=0; j<G.vexnum; j++){
            	//如果源点到顶点j的距离 > 源点经过顶点v再到达j的距离
                if(!S[j] && dist[j] > dist[v]+G.Edge[v][j]){ 
                    dist[j] = G.Edge[v][j] + dist[v]; //更新最小距离和前驱结点
                    path[j] = v; //j的前驱结点为v
                }
            }
        }
    }
}

输出最短路径及路径长度

void PrintPath(MGraph &G, int u, int dist[], int path[]){
    cout<<"各顶点到顶点"<<u<<"的最短路径为:"<<endl;
    for(int i=0; i<G.vexnum; i++){
        int j=i;
        while(j!=u){
            cout<<G.Vertex[j]<<" ";
            if(j!=u) j=path[j];
        }
        cout<<G.Vertex[j]<<"   dist="<<dist[i]<<endl;
    }
}

对于下图,运行结果为:

图的最短路径的Dijkstra算法及Floyd算法_第1张图片 图的最短路径的Dijkstra算法及Floyd算法_第2张图片
需要注意的是,边上带有负权值时,Dijkstra算法并不适用。

求各个顶点之间最短路径的Floyd算法

Floyd算法用于求所有顶点之间的最短路径。
求所有顶点之间的最短路径问题的提法是:已知一个带权有向图,对每一对顶点 v i ≠ v j v_i≠v_j vi=vj,要求求出 v i v_i vi v j v_j vj之间的最短路径和最短路径长度

Floyd算法的基本思想:
设置一个 n × n n×n n×n的方阵 A ( k ) A^{(k)} A(k),其中除对角线的元素都等于0外,其他元素 a ( k ) [ i ] [ j ] ( i ≠ j ) a^{(k)}[i][j](i≠j) a(k)[i][j]i=j表示从顶点 v i v_i vi到顶点 v j v_j vj的路径长度, k k k表示绕行第 k k k个顶点的运算步骤。

算法步骤:
<1>初始时,对于任意两个顶点 v i v_i vi v j v_j vj,若它们之间存在边,则以此边上的权值作为它们之间的最短路径长度;若它们之间不存在有向边,则以maxweight(机器可表示的在问题中不会遇到的最大数,表示∞)作为它们之间的最短路径长度。
<2>以后逐步尝试在原路径上加入顶点 k ( k = 0 , 1 , … … , n − 1 ) k(k=0,1,……,n-1) k(k=0,1,n1)作为中间顶点。如果增加中间顶点后得到的路径比原来的路径长度减少了,则以此新路径代替原路径。

代码实现:

void Floyd(MGraph &G, int A[][maxvertexnum], int P[][maxvertexnum]){
    //A[i][j]是顶点i和顶点j之间的最短路径长度
    //初始化
    for(int i=0; i<G.vexnum; i++){
        for(int j=0; j<G.vexnum; j++){
            A[i][j] = G.Edge[i][j]; //A矩阵初始时其实是邻接矩阵
            P[i][j] = j; //记录路径的数组,记录对应点的最小路径的前驱点
        }
    }
    //对每一个k,产生A(k)
    for(int k=0; k<G.vexnum; k++){
        for(int i=0; i<G.vexnum; i++){
            if(i != k){
                for(int j=0; j<G.vexnum; j++){
                    if(j!=k && A[i][k]+A[k][j] < A[i][j]){
                        A[i][j] = A[i][k]+A[k][j];
                        P[i][j] = P[i][k];
                    }
                }
            }
        }
    }
}

输出最短路径及路径长度:

void PrintPath(MGraph &G, int A[][maxvertexnum], int P[][maxvertexnum]){
    for(int i=0; i<G.vexnum; i++){
        for(int j=0; j<G.vexnum; j++){
            if(i!=j){
                cout<<i<<"->"<<j<<"的路径:";
                int k = P[i][j];
                cout<<i<<"->";
                while(k!=j){
                    cout<<k<<"->";
                    k = P[k][j];
                }
                cout<<j<<setw(10)<<",dist = "<<A[i][j]<<endl;
                cout<<endl;
            }
        }
    }
}

输入及运行结果:

图的最短路径的Dijkstra算法及Floyd算法_第3张图片图的最短路径的Dijkstra算法及Floyd算法_第4张图片   图的最短路径的Dijkstra算法及Floyd算法_第5张图片
需要注意的是,Floyd算法允许图中有带负权值的边,但不允许有包含带负权值的边组成的回路。

完整代码

关于Dijkstra算法与Floyd算法的完整运行代码,由于文章篇幅原因不再给出,可到我的资源中免费下载:最短路径的Dijkstra算法及Floyd算法 。

你可能感兴趣的:(经典编程题——图论,图论,算法,数据结构,dijkstra,floyd)