紫书第十一章-----图论模型与算法(最短路径Dijkstra算法Bellman-Ford算法Floyd算法)

最短路径算法一之Dijkstra算法

算法描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。
使用条件:单源最短路径,适用于边权非负的情况

Dijkstra算法求最短路径具体过程图解
《紫书第十一章-----图论模型与算法(最短路径Dijkstra算法Bellman-Ford算法Floyd算法)》

结合上图具体搜索过程,我绘出下表,方便理解该过程!下表是按照上图的搜索过程来绘制的,当然,存储图的时候,节点存储顺序的不同也会导致搜索的顺序不同,但是可以保证的是每个定点和每条边都搜索仅仅一次哦!

《紫书第十一章-----图论模型与算法(最短路径Dijkstra算法Bellman-Ford算法Floyd算法)》

经过以上的学习,我们能够感觉到Dijkstra算法和bfs算法非常类似,做过下面的两道题目之后就能很好地感悟到这一点啦~~~
下面将是第一道最短路径例题,帮我理解Dijkstra算法,有了前面的讲解和图解,图表示范,下面的代码就比较容易理解了!

【例题 本题代码中的算法既给出了普通版本的代码,同时给出了优先队列优化版本的代码】

输入n和m,代表n个节点,m条边,然后是m行输入,每行有x,y,z,代表x到y的路距离为z。问题:从1出发到各点的最短路径。

参考代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

const int maxn=105;
const int INF=1<<30;
int mp[maxn][maxn];
bool vis[maxn];
int dis[maxn];
int path[maxn];
int n,m;
//注意该搜索算法中,可以认为所有的节点之间都有边,
//其实就是把那些没有连接起来的节点的边的权重看成了
//正无穷

void init()
{
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            mp[i][j]=INF;
}

void Dijkstra(int st)
{
    memset(vis,0,sizeof(vis));
    for(int i=0;i<=n;i++)
        path[i]=-1;
    for(int i=0;i<=n;i++)
        dis[i]=INF;
    dis[st]=0;
    for(int i=0;i<n;i++)
    {
        int pos=0;
        int mmin=INF;
        for(int j=1;j<=n;j++)           // 找到一个新的向外扩展搜索的起点
        {
            if(!vis[j] && dis[j]<mmin)
            {
                pos=j;
                mmin=dis[j];
            }
        }
        if(mmin==INF)break;
        vis[pos]=1;
        for(int j=1;j<=n;j++)
        {
            if(dis[j]>dis[pos]+mp[pos][j] && mp[pos][j]!=INF)  //相当于动态规划里面的状态转移,
            {                           //dis[i]表示的状态是起点st到i的最短路径的距离
                dis[j]=dis[pos]+mp[pos][j];
                path[j]=pos;   //用来记录路径,这个可以对照上面讲过的东东看一下应该可以看懂滴
            }
        }
    }
}

void print(int en)
{
    if(en==-1)return;
    print(path[en]);
    cout<<en<<"->";
}

int main()
{
    int x,y,z;
    int en;
    cin>>n>>m;
    init(); //对mp初始化,一定要记住此语句在输入n之后,我被这一点坑惨了,找了好久才发现
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        mp[x][y]=mp[y][x]=z;
    }
    Dijkstra(1);
    cin>>en;
    cout<<dis[en]<<endl;
    //打印路径信息
    print(path[en]);
    cout<<en<<endl;//打印出最后一个与前面的->配对
    return 0;
}

不妨根据知识点讲解部分的图,给出测试数据样例如下:
input

6 9
1 2 7
1 3 9
1 6 14
2 3 10
2 4 15
3 4 11
3 6 2
4 5 6
5 6 9
6

output

《紫书第十一章-----图论模型与算法(最短路径Dijkstra算法Bellman-Ford算法Floyd算法)》

【在使用优先队列优化代码之前,先补充memset和fill的区别】

  1. memset函数
    按照字节填充某字符
    在头文件”cstring”里面
  2. fill函数
    按照单元赋值,将一个区间的元素都赋同一个值
    在头文件”algorithm”里面

    因为memset函数按照字节填充,所以一般memset只能用来填充char、bool型数组,(因为只有char、bool型占一个字节)如果填充int型数组,除了0和-1,其他的不能。因为只有00000000 = 0,-1同理,如果我们把每一位都填充“1”,会导致变成填充入“11111111”
    而fill函数可以赋值任何,而且使用方法特别简便:
    fill(arr, arr + n, 要填入的内容);

例如:

#include<iostream>
#include<algorithm>

using namespace std;

int main()
{
    int a[100];
    fill(a,a+20,100);
    for(int i=0;i<20;i++)
        cout<<a[i]<<endl;
    return 0;
}

本例题优先队列优化代码如下所示:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>

using namespace std;

const int INF=1<<30;
const int maxn=300;

int n,m,en;
int mp[maxn][maxn];
int dis[maxn];
int path[maxn];
bool vis[maxn];
typedef pair<int ,int>pr;//first是到点的边的长度,second是点

void init()
{
    fill(mp[0],mp[0]+maxn*maxn,INF);
    fill(dis,dis+maxn,INF);
    memset(vis,0,sizeof(vis));
    memset(path,-1,sizeof(path));
}

void Dijkstra(int st)
{
    dis[st]=0;
    priority_queue<pr,vector<pr>,greater<pr> >que;
    que.push(pr(0,st));
    while(!que.empty())
    {
        pr p=que.top();
        que.pop();
        int vi=p.second;
        if(vis[vi])
            continue;
        vis[vi]=1;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i] && dis[i]>dis[vi]+mp[vi][i])
            {
                dis[i]=dis[vi]+mp[vi][i];
                que.push(pr(dis[i],i));
                path[i]=vi;
            }
        }
    }
}

void print(int st)
{
    if(st==-1)return;
    print(path[st]);
    cout<<st<<"->";
}

int main()
{
    while(cin>>n>>m)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            int from,to,len;
            cin>>from>>to>>len;
            mp[from][to]=mp[to][from]=len;
        }
        Dijkstra(1);
        cin>>en;
        cout<<dis[en]<<endl;
        print(path[en]);
        cout<<en<<endl;
    }
    return 0;
}

【类似习题 Choose the best route HDU – 2680 】

One day , Kiki wants to visit one of her friends. As she is liable to carsickness , she wants to arrive at her friend’s home as soon as possible . Now give you a map of the city’s traffic route, and the stations which are near Kiki’s home so that she can take. You may suppose Kiki can change the bus at any station. Please find out the least time Kiki needs to spend. To make it easy, if the city have n bus stations ,the stations will been expressed as an integer 1,2,3…n.

Input

There are several test cases.  Each case begins with three integers n, m and s,(n<1000,m<20000,1=<s<=n) n stands for the number of bus stations in this city and m stands for the number of directed ways between bus stations .(Maybe there are several ways between two bus stations .) s stands for the bus station that near Kiki’s friend’s home. 
Then follow m lines ,each line contains three integers p , q , t (0<t<=1000). means from station p to station q there is a way and it will costs t minutes . 
Then a line with an integer w(0<w<n), means the number of stations Kiki can take at the beginning. Then follows w integers stands for these stations. 
Output
The output contains one line for each data set : the least time Kiki needs to spend ,if it’s impossible to find such a route ,just output “-1”.
Sample Input
5 8 5
1 2 2
1 5 3
1 3 4
2 4 7
2 5 6
2 3 5
3 5 1
4 5 1
2
2 3
4 3 4
1 2 3
1 3 4
2 3 2
1
1
Sample Output
1
-1
【分析   套用Dijkstra算法模板+引入超级源点或把图反向(逆向思维)】   
自己多加一个超级源点,把起点集合连接到超级源点上,然后将起点与超级源点的集合的路径长度设为0,这样就称为一个n+1个点的单源最短路算法。。。。。

AC代码(一):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=1005;
const int INF=1<<30;

int mp[maxn][maxn];
int dis[maxn];
bool vis[maxn];

int n,m,s;

void init()
{
    fill(mp[0],mp[0]+maxn*maxn,INF);
    fill(dis,dis+maxn,INF);;
    memset(vis,0,sizeof(vis));
}

void Dijkstra(int st)
{
    dis[st]=0;
    for(int i=0;i<=n;i++)
    {
        int mmin=INF;
        int pos=0;
        for(int j=0;j<=n;j++)
        {
            if(!vis[j] && dis[j]<mmin)
            {
                mmin=dis[j];
                pos=j;
            }
        }
        if(mmin==INF)break;//已经完成搜索,结束
        vis[pos]=1;
        for(int j=0;j<=n;j++)
        {
            if(dis[j]>dis[pos]+mp[pos][j] && mp[pos][j]!=INF)
                dis[j]=dis[pos]+mp[pos][j];
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m>>s)
    {
        init();
        int p,q,t,w,ss;
        int temp[maxn];
        for(int i=0;i<m;i++)
        {
            cin>>p>>q>>t;
            if(t<mp[p][q])//两个车站之间可能花费不同时间,取最小值,坑点
                mp[p][q]=t;//还要注意本图是有向图
        }

        cin>>w;
        while(w--)
        {
            cin>>ss;
            mp[0][ss]=0;
            dis[ss]=0;//该句代码可有可无,这一点与优先队列优化的代码不同,在优化的代码中再说原因
        }
        Dijkstra(0);
        if(dis[s]==INF)cout<<"-1"<<endl;
        else cout<<dis[s]<<endl;
    }
    return 0;
}

AC代码(二):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=1005;
const int INF=1<<30;

int mp[maxn][maxn];
int dis[maxn];
bool vis[maxn];

int n,m,s;

void init()
{
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
        {
            mp[i][j]=INF;
            //下面这种初始化方式,其实没有必要,因为从a车站到a车站虽说距离是0
            //但根本不会这么走,因为这么走始终没有前进,没有意义
            //if(i==j)mp[i][j]=0;
            //else mp[i][j]=INF;
        }
}

void Dijkstra(int st)
{
    memset(vis,0,sizeof(vis));
    for(int i=0;i<=n;i++)
        dis[i]=mp[0][i];
    dis[st]=0;
    for(int i=0;i<n;i++)
    {
        int mmin=INF;
        int pos=0;
        for(int j=1;j<=n;j++)
        {
            if(!vis[j] && dis[j]<mmin)
            {
                mmin=dis[j];
                pos=j;
            }
        }
        if(mmin==INF)break;//已经完成搜索,结束
        vis[pos]=1;
        for(int j=1;j<=n;j++)
        {
            if(dis[j]>dis[pos]+mp[pos][j] && mp[pos][j]!=INF)
                dis[j]=dis[pos]+mp[pos][j];
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m>>s)
    {
        init();
        int p,q,t,w,ss;
        int temp[maxn];
        for(int i=0;i<m;i++)
        {
            cin>>p>>q>>t;
            if(t<mp[q][p])  //把图反向,并且两个车站之间可能花费不同时间,取最小值,坑点
                mp[q][p]=t;
        }
        Dijkstra(s);
        int ans=INF;
        cin>>w;
        while(w--)
        {
            cin>>ss;
            ans=min(ans,dis[ss]);
        }

        if(ans==INF)cout<<"-1"<<endl;
        else cout<<ans<<endl;
    }
    return 0;
}

AC代码(三)(优先队列优化版本,根据反向图编写):

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int INF=1<<30;
const int maxn=1005;

int mp[maxn][maxn];
int dis[maxn];
bool vis[maxn];
int n,m,s;
int w;
int from,to,weight;

typedef pair<int,int>pr;

void init()
{
    memset(vis,0,sizeof(vis));
    fill(mp[0],mp[0]+maxn*maxn,INF);
    fill(dis,dis+maxn,INF);
}

void Dijkstra(int st)
{
    dis[st]=0;
    priority_queue<pr,vector<pr>,greater<pr> >que;
    que.push(make_pair(0,st));
    while(!que.empty())
    {
        pr p=que.top();
        que.pop();
        int vi=p.second;
        if(vis[vi])continue;
        vis[vi]=1;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i] && dis[i]>dis[vi]+mp[vi][i])
            {
                dis[i]=dis[vi]+mp[vi][i];
                que.push(make_pair(dis[i],i));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m>>s)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            cin>>from>>to>>weight;
            if(weight<mp[to][from])
                mp[to][from]=weight;
        }
        Dijkstra(s);
        cin>>w;
        int tmp;
        int ans=INF;
        while(w--)
        {
            cin>>tmp;
            ans=min(ans,dis[tmp]);
        }
        if(ans==INF)cout<<"-1"<<endl;
        else cout<<ans<<endl;
    }
    return 0;
}

AC代码(四)(用优先队列优化,根据添加超级源点编写):

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int INF=1<<30;
const int maxn=1005;

int mp[maxn][maxn];
int dis[maxn];
bool vis[maxn];
int n,m,s;
int w;
int from,to,weight;

typedef pair<int,int>pr;

void init()
{
    memset(vis,0,sizeof(vis));
    fill(mp[0],mp[0]+maxn*maxn,INF);
    fill(dis,dis+maxn,INF);
}

void Dijkstra(int st)
{
    dis[st]=0;
    priority_queue<pr,vector<pr>,greater<pr> >que;
    que.push(make_pair(0,st));
    while(!que.empty())
    {
        pr p=que.top();
        que.pop();
        int vi=p.second;
        if(vis[vi])continue;
        vis[vi]=1;
        for(int i=0;i<=n;i++)
        {
            if(!vis[i] && dis[i]>dis[vi]+mp[vi][i])
            {
                dis[i]=dis[vi]+mp[vi][i];
                que.push(make_pair(dis[i],i));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m>>s)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            cin>>from>>to>>weight;
            if(weight<mp[from][to])
                mp[from][to]=weight;
        }
        cin>>w;
        int tmp;
        while(w--)
        {
            cin>>tmp;
            mp[0][tmp]=0;
            //dis[tmp]=0;//注意不要写这句,否则错误,因为如果添加了这句代码,距离提前初始化,导致
                         //队列中只进入了一个元素,即超级源点,然后出队,算法结束,这一点和非优化版
                        //的代码则不一样,非优化版的代码中,即使刚开始在松弛部分代码if(!vis[i] 
                        //&& dis[i]>dis[vi]+mp[vi][i])一次也不执行也没关系,后续会继续执行
                    //Dijkstra算法,但是优化版本的则会结束掉Dijkstra算法。 
        }
        Dijkstra(0);
        if(dis[s]==INF)cout<<"-1"<<endl;
        else cout<<dis[s]<<endl;
    }
    return 0;
}

最短路径算法二之Bellman-Ford算法

回忆前面的Dijkstra算法,只能解决边权非负的情况,如果边权存在负值则无法适用(若有负边权的话,可以反复走这条边,这样的话,路径长度变成了无穷小,不存在最短路径了),怎么办呢?Bellman-Ford算法来解决!
主要思想:对所有的边进行n-1轮松弛操作,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1边。换句话说,第1轮在对所有的边进行松弛后,得到的是从1号顶点只能经过一条边到达其余各定点的最短路径长度。第2轮在对所有的边进行松弛后,得到的是从1号顶点只能经过两条边到达其余各定点的最短路径长度,……
[Bellman-Ford算法概述(百度百科)](https://baike.baidu.com/item/Bellman-Ford%E7%AE%97%E6%B3%95/1089090?fr=aladdin)
Bellman – ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

注意此算法和Dijkstra算法都只是适用于单源路径,Bellman-Ford算法可以含负权边,而Dijkstra算法则不能含有负权边。

  • 参看刘汝佳的《算法竞赛入门经典(第二版)》363页如下,帮助理解这个算法~

首先确定一个事实:如果最短路存在,一定存在一个不含环的最短路。理由:在边权可正可负的图中,环有零环、正环和负环三种。如果包含零环或正环,去掉后路径不会变长;如果包含负环,则意味着最短路不存在(负无穷)。既然不存在环,最短路最多只经过(不算起点)n-1的节点,可以通过n-1轮的松弛操作得到。

  • Bellman-Ford算法可以大致分为三个部分

第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。

【例题 最短路 HDU – 2544 】
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

Input
输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。
Output
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
Sample Output
3
2

【分析】
本题可以使用Dijkstra算法编程,直接仿照Dijkstra讲解部分及模板代码编程即可解决。这里重点依据此水题来讲解Bellman-Ford算法的代码实现及其优化。
点此链接直击本题代码参考博文

AC代码(非优化版本):

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int INF=1<<30;
const int maxn=1005;
const int edge_maxn=20005;

int n,m;
int from,to,weight;
bool loop;
int dis[maxn];

typedef struct
{
    int s,e,w;
}Edge;

Edge edge[edge_maxn*2];//无向图看成有向图处理

void bellman_ford(int st)
{
    fill(dis,dis+maxn,INF);
    dis[st]=0;
    //step1:对边进行松弛更新操作 
    for(int i=1;i<=n-1;i++)
    {
        bool ok=0;
        for(int j=1;j<=m;j++)
        {
            if(dis[edge[j].e]>dis[edge[j].s]+edge[j].w)
            {
                dis[edge[j].e]=dis[edge[j].s]+edge[j].w;
                ok=1;
            }
        }
        if(!ok)break;
    }
    //step2:判断图中是否有负权环
    loop=0;
    for(int i=1;i<=m;i++)
    {
        if(dis[edge[i].e]>dis[edge[i].s]+edge[i].w)
        {
            loop=1;
            break;
        }
    }
}

int main()
{
    while(cin>>n>>m)
    {
        if(n==0 && m==0)break;
        int cnt=1;
        for(int i=1;i<=m;i++)
        {
            cin>>from>>to>>weight;
            edge[cnt].s=edge[cnt+1].e=from;
            edge[cnt].e=edge[cnt+1].s=to;
            edge[cnt++].w=weight;
            edge[cnt++].w=weight;
        }
        m*=2;
        bellman_ford(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

AC代码(队列优化版本):

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

const int INF=1<<30;
const int maxn=1005;

int n,m;
int from,to,weight;
int mp[maxn][maxn];
bool vis[maxn];
int dis[maxn];

void init()
{
    memset(vis,0,sizeof(vis));
    fill(mp[0],mp[0]+maxn*maxn,INF);
    fill(dis,dis+maxn,INF);
}

void bellman_ford(int st)
{
    dis[st]=0;
    queue<int>que;
    que.push(st);
    vis[st]=1;
    //取出队首元素点,对其相邻点进行松弛操作,如果在队列外,则加入队列,
    //和Dijkstra算法的优化类似,也和bfs思想类似
    while(!que.empty())
    {
        int from=que.front();
        que.pop();
        vis[from]=0;
        for(int i=1;i<=n;i++)
        {
            if(dis[from]+mp[from][i]<dis[i])
            {
                dis[i]=dis[from]+mp[from][i];
                que.push(i);
                vis[i]=1;
            }
        }
    }
}

int main()
{
    while(cin>>n>>m)
    {
        init();
        if(n==0 && m==0)break;
        for(int i=1;i<=m;i++)
        {
            cin>>from>>to>>weight;
            mp[from][to]=mp[to][from]=weight;
        }
        bellman_ford(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

最短路径算法三之Floyd算法

Dijkstra算法和Bellman-Ford算法都只适用于求单源路径最短路问题,如果要求出任一点到其他所有点的最短路径,不必调用n次Dijkstra算法或者Bellman-Ford算法,可以直接用Floyd算法即可,代码非常简短容易记忆,因为这个代码和矩阵乘法长的太像了啊233333333333333

【例题 依旧是讲解Bellman-Ford算法中用到的例题hdu2544】

AC代码:

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int inf=1e6+1;//floyd算法中要注意inf的值,设置为
                    //比最短路径长度上限恰好大一点点,
                    //因为该算法中牵扯到mp数组元素的加法
                    //运算,如果inf比较大,可能会溢出
const int maxn=1005;

int n,m;
int mp[maxn][maxn];//mp[i][j]表示i点到j点的最短距离

void init()//本算法其实用了动态规划的思想,对状态初始化
{
    fill(mp[0],mp[0]+maxn*maxn,inf);
    for(int i=0;i<=n;i++)
        mp[i][i]=0;
}
//该算法的实现代码形式上和矩阵乘法非常相似,便于记忆
void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(mp[i][k]+mp[k][j]<mp[i][j])
                    mp[i][j]=mp[i][k]+mp[k][j];
}

int main()
{
    while(cin>>n>>m && n && m)
    {
        init();
        int from,to,weight;
        for(int i=1;i<=m;i++)
        {
            cin>>from>>to>>weight;
            mp[from][to]=mp[to][from]=weight;
        }
        floyd();
        cout<<mp[1][n]<<endl;
    }
    return 0;
}
    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/ccnuacmhdu/article/details/79260131
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞