【algo&ds】7.最短路径问题

此生再无相见时 提交于 2019-12-05 11:12:18
  • 单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径
    • (有向)无权图:BFS
    • (有向)有权图:Dijkstra算法
  • 多源最短路径问题:求任意两顶点间的最短路径
    • 直接将单源最短路算法调用|V|遍
    • Floyd算法

1.BFS算法求解单源无权图最短路径

1.1算法描述

广度优先搜索,开一个额外的数组存储每一个结点的访问状态,一层一层(取出队首元素,遍历所有相邻且未被访问的结点)的入队列,然后层数++

这里的额外数组就是dist[w],指的是从源点到顶点w的最短路径长度,初始化为-1,判断未访问即==-1,如果未访问且存在边G[v][w]则dist[w] = dist[v] +1 ;

path数组用于保存每一个顶点w的前驱顶点v,也即这条最短路径(s->w)必定是从(s->....->v->w),通过栈来逆序输出path[w] 、path[path[w]]....

更加详细的算法示例可以参考视频

1.2代码实现

#include<iostream>
#include<stdlib.h>
#include<cstdlib>
#include<queue>
#include<stack>
#define Init -1
#define MaxVertex  100
int path[MaxVertex];  // 存储路径,如果当前顶点v出队列,且存在顶点v->w的路径,则path[w] = v
int dist[MaxVertex];  // 存储路径长度,即从源顶点s到当前顶点w的最短路径dist[w]
int G[MaxVertex][MaxVertex]; // 图,采用邻接矩阵表示
int Ne;  // 顶点数 
int Nv;  // 边 
typedef int Vertex;
using namespace std;


void build(){
    int v1,v2;
    // 初始化点 
    cin>>Nv;
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = 0;
    // 初始化路径
    for(int i=1;i<=Nv;i++)
        path[i] = Init;
    // 初始化路径长度
    for(int i=1;i<=Nv;i++)
         dist[i] = Init;
    // 初始化边 
    cin>>Ne;
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2;
        G[v1][v2] = 1; // 有向图! 
    }
}

void Unweighted(Vertex v){
    queue<Vertex> q;
    dist[v] = 0;  // 将自己的距离置 0 ,路径path[v]不变
    Vertex w;
    q.push(v);
    while(!q.empty()){
         w = q.front();
         q.pop();
         for(int i=1;i<=Nv;i++)
            // 如果没被访问过,且连通 
            if(dist[i]==Init && G[w][i]){
                dist[i] = dist[w]+1;  // 是上一步的距离 + 1 
                path[i] = w;  // w 是上一步要走路径的下一步路径 
                q.push(i);
            }
    }
}

// 获取路径 
void getTail(Vertex v){
    for(int i=1;i<=Nv;i++){
        if(i==v)
            continue;
        stack<Vertex> s;
        cout<<v<<"到"<<i<<"的最短距离是:"<<dist[i];
        Vertex w = i;
        // 当没到达起始起点前一直做循环 
        while(path[w]!=Init){
            s.push(w);  // 入栈 
            w = path[w];
        }
        // 逆序输出入栈元素,得到路径 
        cout<<"    其路径为:";
        if(v != i)
            cout<<v;
        while(!s.empty()){
            // 输出栈顶元素 
            cout<<"→"<<s.top();
            s.pop(); // 出栈 
        }
        cout<<endl;
    }
}


int main(){
    build();
    Unweighted(3);
    getTail(3); 
    return 0;
}

2.Dijkstra算法求解单源有权图最短路径

2.1算法描述

有权图的单源最短路算法可以使用Dijkstra算法实现,算法的伪码描述如下:

void Dijkstra( Vertex s ) {
    while (1) {
        V = 未收录顶点中dist最小者;
        if ( 这样的V不存在)
            break;
        collected[V] = true;
        for ( V 的每个邻接点W )
            if ( collected[W] == false )
                if ( dist[V]+E<V,W> < dist[W] ) {
                    dist[W] = dist[V] + E<V,W> ;
                    path[W] = V;
                }
    }
} /* 不能解决有负边的情况*/

引出了两个问题:

  • 如何确定未收录顶点中dist最小者?
  • 如何初始化dist[i]?

如何确定未收录顶点中dist最小者?

1.直接扫描所有未收录顶点,时间复杂度为– O( |V| ),总的时间复杂度为T = O( |V|^2 + |E| )对于稠密图效果好

2.将dist存在最小堆中,时间复杂度为– O( log|V| ),总的时间复杂度为T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| ),对于稀疏图效果好。

如何初始化dist[i]?

  • 对于dist[0],也就是源点可以直接初始化为0
  • 对于存在边G[0][w],则dist[w]可以直接初始化为顶点s到顶点w的边权
  • 其它的顶点w,初始化为infinity(无穷大)

  • 初始状态,两个数组的初始化如上图所示

对于算法的详细示例可以参考视频

2.2代码实现

#include<iostream>
#include<stdlib.h>
#define Inf 1000000
#define Init -1
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex];  // 距离 
int path[MaxVertex];  // 路径 
int collected[MaxVertex];  // 被收录集合 
int Nv;   // 顶点 
int Ne;   // 边 
using namespace std;

// 初始化图信息 
void build(){
    Vertex v1,v2;
    int w;
    cin>>Nv;
    // 初始化图 
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = 0;
    // 初始化路径 
    for(int i=1;i<=Nv;i++)
        path[i] = Init;
    // 初始化距离
    for(int i=0;i<=Nv;i++)
        dist[i] = Inf;
    // 初始化收录情况 
    for(int i=1;i<=Nv;i++)
        collected[i] = false;
    cin>>Ne;
    // 初始化点
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2>>w;
        G[v1][v2] = w;  // 有向图 
    }
}

// 初始化距离和路径信息 
void crate(Vertex s){
    dist[s] = 0;
    collected[s] = true;
    for(int i=1;i<=Nv;i++)
        if(G[s][i]){
            dist[i] = G[s][i];
            path[i] = s;
        }
}

// 查找未收录顶点中dist最小者
Vertex FindMin(Vertex s){
    int min = 0;  // 之前特地把 dist[0] 初始化为正无穷 
    for(Vertex i=1;i<=Nv;i++)
        if(i != s && dist[i] < dist[min] && !collected[i])
            min = i;
    return min;
}


void Dijkstra(Vertex s){
    crate(s); 
    while(true){
        Vertex V = FindMin(s);   // 找到 
        if(!V)
            break;
        collected[V] = true;  //收录
        for(Vertex W=1;W<=Nv;W++)
            if(!collected[W] && G[V][W]){  // 如果未被收录
                if(dist[V] + G[V][W] < dist[W]){
                    dist[W] = G[V][W] + dist[V];
                    path[W] = V;
                }
            }
    }
}

void output(){
    for(int i=1;i<=Nv;i++)
        cout<<dist[i]<<" ";
    cout<<endl;
    for(int i=1;i<=Nv;i++)
        cout<<path[i]<<" ";
    cout<<endl;
}


int main(){
    build();
    Dijkstra(1);
    output();
    return 0;
}

3.Floyd算法求解多源最短路径算法

#include<iostream>
#include<stdlib.h>
#define INF 1000000
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex][MaxVertex];  // 距离 
int path[MaxVertex][MaxVertex];  // 路径 
int Nv;   // 顶点 
int Ne;   // 边 
using namespace std;

// 初始化图信息 
void build(){
    Vertex v1,v2;
    int w;
    cin>>Nv;
    // 初始化图 
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = INF;
    cin>>Ne;
    // 初始化点
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2>>w;
        G[v1][v2] = w;  
        G[v2][v1] = w;
    }
}

void Floyd(){
    for(Vertex i=1;i<=Nv;i++)
        for(Vertex j=1;j<=Nv;j++){
            dist[i][j] = G[i][j];
            path[i][j] = -1;
        }
    for(Vertex k=1;k<=Nv;k++)
        for(Vertex i=1;i<=Nv;i++)
            for(Vertex j=1;j<=Nv;j++)
                if(dist[i][k] + dist[k][j] < dist[i][j]){
                    dist[i][j] = dist[i][k] + dist[k][j];
                    path[i][j] = k;
                }
} 

void output(){
    for(Vertex i=1;i<=Nv;i++){ 
        for(Vertex j=1;j<=Nv;j++)
            cout<<dist[i][j]<<" ";  
        cout<<endl;
    }
    cout<<endl;
    for(Vertex i=1;i<=Nv;i++){ 
        for(Vertex j=1;j<=Nv;j++)
            cout<<path[i][j]<<" ";  
        cout<<endl;
    }
}


int main(){
    build();
    Floyd();
    output();
    return 0;
}

更多详细的算法描述请参考视频

以及文章

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!