图论 —— 最短路 —— Dijkstra 算法

【概述】

Dijkstra 算法是单源最短路径算法,即计算起点只有一个的情况到其他点的最短路径,其无法处理存在负边权的情况。

其时间复杂度是:O(E+VlogV)

【算法分析】

将点分为两类,一类是已确定最短路径的点,称为:白点,一类是未确定最短路径的点,称为:蓝点。

求一个点的最短路径,就是把这个点由蓝点变为白点,从起点到蓝点的最短路径上的中转点在这个时刻只能是白点。

Dijkstra 算法的思想,就是一开始将起点到终点的距离标记为 0,而后进行 n 次循环,每次找出一个到起点距离 dis[u] 最短的点 u ,将它从蓝点变为白点,随后枚举所有白点 Vi,如果以此白点为中转到达蓝点 Vi 的路径 dis[u]+w[u][vi] 更短的话,这将它作为 Vi 的更短路径(此时还不能确定是不是Vi的最短路径)。

以此类推,每找到一个白点,就尝试用它修改其他所有蓝点,中转点先于终点变成白点,故每一个终点一定能被它的最后一个中转点所修改,从而求得最短路径。

以下图为例

算法开始时,作为起点的 dis[1]=0,其他的点 dis[i]=0x3f3f3f3f

第一轮循环找到 dis[1] 最小,将 1 变为白点,对所有蓝点进行修改,使得:dis[2]=2,dis[3]=4,dis[4]=7

此时,dis[2]、dis[3]、dis[4] 被它的最后一个中转点 1 修改了最短路径。

第二轮循环找到 dis[2] 最小,将 2 变成白点,对所有蓝点进行修改,使得:dis[3]=3、dis[5]=4

此时,dis[3]、dis[5] 被它的最后一个中转点 2 修改了最短路径。

第三轮循环找到 dis[3] 最小,将 3 变成白点,对所有蓝点进行修改,使得:dis[4]=4。

此时,dis[4] 被它的最后一个中转点 3 修改了最短路径,但发现以 3 为中转不能修改 5,说明 3 不是 5 的最后一个中转点。

接下来两轮循环将 4、5 也变成白点。

N轮循环结束,所有点的最短路径均可求出。

【算法描述】

设起点为 s,dis[v] 表示从 s 到 v 的最短路径,pre[v] 为 v 的前驱结点,vis[v] 用于记录 v 是否被访问过。

1.初始化:

dis[v]=0x3f3f3f3f(v≠s),vis[v]=false,即:从始点到各点的值初始化为一极大值,所有点均标记为未访问

dis[s]=0,pre[s]=0,即:始点到始点的距离为 0,且其没有前驱结点

2.算法主体:

for(int i=1;i<=n;i++) {
    int min=INF;
    int u=0;
    
    for(int v=1;v<=n;v++) { //在没有被访问过的点中找一个顶点u,使得dis[u]是最小的
        if( vis[v]==false && dis[v]<min) {
            min=dis[v];
            u=v;
        }
    }
    if(u==0)
        break;
    
    vis[u]=true;//u标记为已确定的最短路径
    for(int v=1;j<=n;j++) { //枚举与u相连的每个未确定的最短路的顶点
        if(dis[u]+w[u][v]<dis[v]) {
            dis[j]=dis[u]+w[u][v]; //更新最短路径
            pre[v]=u;//记录前驱
        }
    }
}

3.算法结束:

dis[v] 即为 s 到 v 最短距离,pre[v] 即为 v 的前驱结点,用来输出路径。

【模版】

1.简化版

简化版不可处理重边图

int dis[N];//单源最短距离
int G[N][N];//G[i][j]表示i到j的有向边长
bool vis[N];//表示w[i]是否已经计算完
void dijkstra(int n,int s){
    for(int i=1;i<=n;i++){
        int x;//x标记当前最短w的点
        int min_dis=INF;//记录当前最小距离
 
        for(int y=1;y<=n;y++){
            if(!vis[y] && min_dis>=dis[y]){
                x=y;
                min_dis=dis[x];
            }
        }

        vis[x]=true;
 
        for(int y=1;y<=n;y++) 
            dis[y]=min(dis[y],dis[x]+G[x][y]);
    }
}
int main(){
    memset(dis,INF,sizeof(dis));
    memset(vis,0,sizeof(vis));

    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y,dis;
        scanf("%d%d%d",&x,&y,&dis);
        G[x][y] = G[y][x] = dis; //无向图添边一次,有向图添边两次
    }
    int start;
    scanf("%d",&start);
    dijkstra(n,start);
    for(int i=1;i<=n;i++)
        printf("%d\n",dis[i]);
    return 0;
}

2.标准版

标准版适用于稀疏图,可处理重边图

int n,m;
struct Edge{//边
    int from;//边的起点
    int to;//边的终点
    int dis;//边的长度
    Edge(int f,int t,int d){//构造函数
        from=f;
        to=t;
        dis=d;
    }
};

struct HeapNode{//Dijkstra用到的优先队列的结点
    int dis;//点到起点距离
    int u;//点的序号
    HeapNode(int a,int b){
        dis=a;
        u=b;
    }
    bool operator < (const HeapNode &rhs) const  {
        return dis > rhs.dis;
    }
};

struct Dijkstra{
    int n,m;//点数、边数
    vector<Edge> edges;//边列表
    vector<int> G[N];//每个结点出发的边的编号
    bool vis[N];//是否走过
    int dis[N];//起点s到各点的距离
    int p[N];//从起点s到i的最短路中的最后一条边的编号

    void init(int n) {//初始化
        this->n = n;
        for(int i=0;i<n;i++) //清空邻接表
            G[i].clear();
        edges.clear();//清空边列表
    }

    void AddEdge(int from,int to,int diss) {//添加边,若为无向图,调用两次
        edges.push_back(Edge(from,to,diss));
        m=edges.size();//边的个数
        G[from].push_back(m-1);//添加至边列表
    }

    int dijkstra(int s) {//求s到所有点的距离

        memset(dis,INF,sizeof(dis));
        memset(vis,false,sizeof(vis));
        dis[s]=0;

        priority_queue<HeapNode> Q;//优先队列
        Q.push(HeapNode(0,s));
        while(!Q.empty()) {
            HeapNode x=Q.top();
            Q.pop();

            int u=x.u;
            if(vis[u])//若已被访问
                continue;

            vis[u]=true;//标记为已访问
            for(int i=0;i<G[u].size();i++) {//枚举所有与当前点相连的边
                Edge &e=edges[G[u][i]];
                if(dis[e.to] > dis[u]+e.dis) {//更新距离
                    dis[e.to] = dis[u]+e.dis;
                    p[e.to]=G[u][i];
                    Q.push(HeapNode(dis[e.to],e.to));
                }
            }
        }
        return dis[n];//返回起点s到终点n最短距离
    }
}DJ;

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
    {
        DJ.init(n);//初始化
        for(int i=0;i<m;i++) {
            int x,y,dis;
            scanf("%d%d%d",&x,&y,&dis);
            //无向图添边两次
            DJ.AddEdge(x,y,dis);
            DJ.AddEdge(y,x,dis);
        }

        int start;
        scanf("%d",&start);
        DJ.dijkstra(start);//求start到各点的距离
        for(int i=0,j=0,s=++start;i<n;i++)//输出start到各点的距离
            printf("%d->%d: %d\n",s,++j,DJ.dis[i]);
    }
    return 0;
}

 

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值