Dijkstra算法

Dijkstra算法

单源最短路问题
可以求出指定某个点到其他所有点的最短距离

构建

邻接矩阵的构造
没有相连接的两个结点的距离时无穷大,本身到本身为0,其他正常
1.先把所有的结点的距离初始化为无穷大
2.本身与本身的距离设为0
3.输入距离

距离数组的构造
构造一个一维距离数组来记录该点到其他点的最短距离
1.进行赋值,从邻接矩阵中赋值
2.本身到本身设为0

复杂度分析

O((o^{2}))
优化后O((nlog_{n}))

思路

对每一个点进行遍历
找个该点的相连点中,距离该点最近的点,然后标记最近点
从标记的最近点进行遍历最近点相连接的点,进行距离更新

因为距离用的是指定某个点的距离,所以最后求出来的距离数组是对于该点来说的
相当于求出某个点a到另个点b的最短距离,然后这个距离和指定点到b的距离进行比大小,求距离短的那条路

比如
遍历为1时,表示结点为1的点进行查找,找到1的最近点,
然后对该点的连接点进行查找,比较指定点到这个点的距离和指定点先到1再到这个点的距离哪个近,近的变成指定点到该点的距离

最短路问题模板

传送门

输出最短距离

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define INF 1000000
using namespace std;
const int maxn=1005;
int edges[maxn][maxn];//邻接矩阵表示
int n;//点的数
int m;//边数
int dis[maxn];//储存指定点到其他点的距离
int vis[maxn];//标记顶点是否被访问过
void init(){
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            if(i==j)edges[i][j]=0;//到本身的距离
            else edges[i][j]=INF;//无穷大
        }
        vis[i]=0;
    }
    int a,b,d;
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&d);
        if(edges[a][b]>d){
            edges[a][b]=edges[b][a]=d;
        }
    }
}

void Dij(int s){
    for(int i=1;i<=n;i++){
        dis[i]=edges[s][i];
    }
    vis[s]=1;//初始点标记
    int minn,mark;//进行查找,找到与当前点相连接的且距离最短的一个点,进行标记
    for(int i=1;i<=n;i++){
        minn=INF;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&dis[j]<minn){
                minn=dis[j];
                mark=j;
            }
        }
        vis[mark]=1;//标记该点
        //对这个标记点的相连点进行距离更新
        for(int k=1;k<=n;k++){//遍历所有点
            if(edges[mark][k]<INF){//如果说当前的点与前面标记的点相互连接
                if(vis[k]==0&&dis[k]>dis[mark]+edges[mark][k]){//进行判断,看看是否可以更新为更小的的距离
                    //dis[k]是指定点到该点的距离,dis[mark]是指定点标记点的距离,edges[mark][k]是标记点到该点的距离
                    dis[k]=dis[mark]+edges[mark][k];
                }
            }
        }
    }
}

int main(){
    while(scanf("%d%d",&n,&m)==2){
	    if(n==0&&m==0)break;
        init();//初始化并且创建;邻接矩阵
        int s,t;
        scanf("%d%d",&s,&t);
        Dij(s);
        if(dis[t]==INF){
            printf("-1
");
        }else{
            printf("%d
",dis[t]);
        }
    }
    return 0;
}

输出路径

加个path[]一维数组,且在dij算法里两处不断更新
然后用while语句访问,while(path[]!=-1)不断变化下标,但是这样只能倒着输出
可以再创建一个数组或者是栈,使得正序输出

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define INF 1000000
using namespace std;
const int maxn=1005;
int edges[maxn][maxn];//邻接矩阵表示
int n;//点的数
int m;//边数
int dis[maxn];//储存指定点到其他点的距离
int vis[maxn];//标记顶点是否被访问过
int path[maxn];
void init(){
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            if(i==j)edges[i][j]=0;//到本身的距离
            else edges[i][j]=INF;//无穷大
        }
        vis[i]=0;
    }
    int a,b,d;
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&d);
        if(edges[a][b]>d){
            edges[a][b]=edges[b][a]=d;
        }
    }
}

void Dij(int s){
    for(int i=1;i<=n;i++){
        dis[i]=edges[s][i];
        path[i]=-1;
    }
    vis[s]=1;//初始点标记
    int minn,mark;
    for(int i=1;i<=n;i++){
        minn=INF;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&dis[j]<minn){
                minn=dis[j];
                mark=j;
                path[mark]=i;//不断更新
            }
        }
        vis[mark]=1;//标记该点
        for(int k=1;k<=n;k++){
            if(edges[mark][k]<INF){
                if(vis[k]==0&&dis[k]>dis[mark]+edges[mark][k]){
                    dis[k]=dis[mark]+edges[mark][k];
                    path[k]=mark;//不断地更新mark后面地那个点
                }
            }
        }
    }
}

int main(){
    while(scanf("%d%d",&n,&m)==2&&n&&m){
        init();
        int s,t;
        scanf("%d%d",&s,&t);
        Dij(s);
        for(int i=1;i<=n;i++)printf("%d ", path[i]);//-1表示不走,path[]的值是点的前一个位置的下标
    }
    return 0;
}

双重权值

传送门

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=1005;
int edges[maxn][maxn],edgec[maxn][maxn];//邻接矩阵表示
int n,m;//边和点
int dis[maxn],cost[maxn];//储存指定点到其他点的距离
bool vis[maxn];//标记顶点是否被访问过
void init(){
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            if(i==j)edges[i][j]=edgec[i][j]=0;//到本身的距离
            else edges[i][j]=edgec[i][j]=INF;//无穷大
        }
        vis[i]=false;
    }
}
void Dij(int s){
    for(int i=1;i<=n;i++){
        dis[i]=edges[s][i];
        cost[i]=edgec[s][i];
    }
    int minn,mark;
    for(int i=1;i<=n;i++){
        minn=INF;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&dis[j]<minn){
                minn=dis[j];
                mark=j;
            }
        }
        vis[mark]=true;
        for(int k=1;k<=n;k++){
            if(edges[mark][k]<INF){
                if(!vis[k]&&dis[k]>dis[mark]+edges[mark][k]){
                    dis[k]=dis[mark]+edges[mark][k];
                    cost[k]=cost[mark]+edgec[mark][k];
                }else if(!vis[k]&&dis[k]==dis[mark]+edges[mark][k]){
                    if(cost[k]>cost[mark]+edgec[mark][k]){
                        cost[k]=cost[mark]+edgec[mark][k];
                    }
                }
            }
        }
    }
}

int main(){
    while(~scanf("%d%d",&n,&m)){
        if(n==m&&n==0)break;
        init();//初始化并且创建;邻接矩阵
        int a,b,d,p;
        for(int i=0;i<m;i++){
            scanf("%d%d%d%d",&a,&b,&d,&p);
            if(edges[a][b]>d){
                edges[a][b]=edges[b][a]=d;
                edgec[a][b]=edgec[b][a]=p;
            }
        }
        int s,t;
        scanf("%d%d",&s,&t);
        Dij(s);
        printf("%d %d
",dis[t],cost[t]);
    }
    return 0;
}

优化

  • 优先队列
  • 前向星
  • 邻接表代替邻接矩阵

链式前向星+优先队列优化(边少的情况)

/*
输出s到t的最短路
输入n,m
以及m个u,v,w
输出最短路
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=1e5+5,maxm=2e5+5;
int head[maxm],vis[maxn],dis[maxn];//dis[]数组表示到制定点的最短距离,vis[]数组表示某个点是否走过
int n,m,s,t;
int cnt=1;//记录图的结构体的下标
int pre[maxn];//存路径用
struct edge{
    int to;//到哪
    int from;//从哪开始
    int w;
}e[maxm];
inline void add(int u,int v,int w){//链式前向星存图
    e[cnt].to=v;
    e[cnt].w=w;
    e[cnt].from=head[u];
    head[u]=cnt;
    cnt++;
}
struct node{
    int dis;//dis表示到起始点的距离
    int pos;//pos表示该点的下标
    bool operator < (const node &x)const{//符号重载,对距离排序,针对于在优先队列里
        return x.dis<dis;
    }
};
std::priority_queue<node> q;//构造优先队列
void dijkstra(){
    q.push((node){dis[s],s});//把初始位置和距离放进优先队列
    while(!q.empty()){
        node tmp=q.top();
        q.pop();
        int u=tmp.pos;
        if(vis[u])continue;//如果这个点走过了
        vis[u]=1;//标记走过
        for(int i=head[u];i;i=e[i].from){
            int v=e[i].to;
            if(!vis[v]&&dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                pre[v]=u;
                q.push((node){dis[v],v});
              }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    s=1,t=n;//s到t
    for(int i=1;i<=n;i++)dis[i]=INF;//初始化
    dis[s]=0;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);//构成无向图
    }
    dijkstra();
    printf("%d
",dis[t]);//输出1到n的最短路
}

邻接表+优先队列(点少的情况)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=1e5+5;
struct edge{
    int to,cost;
};
int vis[maxn];//表示该点是否走过
int n,m,s,t;//n个点,m个边,s起点,t终点
int dis[maxn];//最短距离
struct HeadNode{
    int d,u;//d为距离,u为结点位置
    bool operator < (const HeadNode& rhs) const{//重载HeadNode结构体的<运算符,如果放在优先队列里,将会按照距离排序
        return d>rhs.d;
    }
};
vector<edge> g[maxn];
void Dij(){
    for(int i=1;i<=n;i++){
        dis[i]=INF;
    }
    dis[s]=0;

    priority_queue<HeadNode>q;
    q.push((HeadNode){0,s});//把类型为HeadNode,值为0,s
    while(!q.empty()){
        HeadNode x = q.top();//x是队列顶
        q.pop();//把顶取出,顶就是距离最短的那个点
        int u=x.u;
        if(vis[u])continue;
        vis[u]=1;
        for(int i=0;i<g[u].size();i++){//遍历与距离最短点相连接的点
            edge e=g[u][i];
            if(dis[e.to]>dis[u]+e.cost){//如果直接到该点的距离大于从通过u点再到该点的距离的话
                dis[e.to]=dis[u]+e.cost;//松弛
                q.push((HeadNode){dis[e.to],e.to});
            }
        }
    }

}

int main(){
    cin>>n>>m>>s>>t;
    for(int i=1;i<=m;i++){
        edge e;
        int x;
        scanf("%d%d%d",&x,&e.to,&e.cost);//x出发到e.to,花费e.cost
        g[x].push_back(e);//只有这个的话就是有向图

        g[e.to].push_back((edge){x,e.cost});//构建无向图
    }
    Dij();
    if(dis[t]==INF)printf("-1
");//不存在最短路
    else printf("%d
",dis[t]);
    return 0;
}

输出路径模板

增加pre[数组],pre[s]=0,反向输出路径


#include<bits/stdc++.h>
using namespace std;
const int inf=1000000002;
const int maxn=100100,maxm=200200;
int head[maxm],vis[maxn],dis[maxn];//dis[]数组表示到制定点的最短距离,vis[]数组表示某个点是否走过
int i,j;
int u,v,w,z;
int n,m,s,t;
int cnt=1;//记录图的结构体的下标
int pre[maxn];
struct edge{
    int to;//到哪
    int from;//从哪开始
    int w;
}e[maxm];
inline void add(int u,int v,int w){
    e[cnt].to=v;
    e[cnt].w=w;
    e[cnt].from=head[u];
    head[u]=cnt;
    cnt++;
}
struct node
{
    int dis;//dis表示到起始点的距离
    int pos;//pos表示该点的下标
    bool operator < (const node &x)const{//符号重载,对距离排序,针对于在优先队列里
        return x.dis<dis;
    }
};
std::priority_queue<node> q;//构造优先队列
inline void dijkstra()
{
    q.push((node){dis[s],s});//把初始位置和距离放进优先队列
    while(!q.empty()){
        node tmp=q.top();
        q.pop();
        int u=tmp.pos;
        if(vis[u])continue;//如果这个点走过了
        vis[u]=1;//标记走过
        for(i=head[u];i;i=e[i].from){
            int v=e[i].to;
            if(!vis[v]&&dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                pre[v]=u;
                q.push((node){dis[v],v});
              }
        }

    }
}
int main(){
    scanf("%d%d",&n,&m);
    s=1;//起始点
    pre[s]=0;
    for(i=1;i<=n;i++)dis[i]=inf;//初始化
    dis[s]=0;

    for(i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dijkstra();
    printf("%d
",dis[n]);

    while(pre[n]!=0){
        printf("%d
",pre[n]);
        n=pre[n];
    }
}

原文地址:https://www.cnblogs.com/Emcikem/p/11333851.html