• 图论四:最短路径算法


    一、广度优先搜索

    1、思路:距离开始点最近的点首先被赋值,最远的点最后被赋值。

    2、适用范围:对于非负数权的无圈图来说(单源最短路径)。

    3、算法实现:

    (1)一个队列记录每个每个节点的编号。

    (2)将起始节点入队,将所有节点到起始节点的距离设置为无穷大,起始节点到起始节点的距离为0;

    (3)取队列的第一个节点,这个节点出队,遍历这个节点相邻的节点,如果这个节点的距离是INF就变为它前一个节点的距离+1,并且入队。

    (4)重复(3)操作,直到所有所有队列为空为止,此时dis数组记录了每一个节点到起始节点的最小距离。

    4、代码:

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<vector>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    vector <int> vc[maxn];
    int dis[maxn];
    void bfs(int n)
    {
        int i,j,tmp;
        for(i=1;i<=n;i++) dis[i]=INF;
        dis[1]=0;
        queue <int> q;
        q.push(1);
        while(!q.empty())
        {
            tmp=q.front();
            q.pop();
            for(i=0;i<vc[tmp].size();i++)
            {
                if(dis[vc[tmp][i]]>dis[tmp])
                {
                    dis[vc[tmp][i]]=dis[tmp]+1;
                    q.push(vc[tmp][i]);
                }
            }
        }
    }
    int main(void)
    {
        int n,m,i,x,y;
        cin>>n>>m;
        for(i=1;i<=m;i++)
        {
            cin>>x>>y;
            vc[x].push_back(y);
        }
        bfs(n); 
        cout<<dis[n]<<endl;
        return 0;
    } 
    View Code

    5、复杂度:O(|V|^2),复杂度较高。

    二、Dijkstra算法

    1、思路:贪心

    2、适用范围:有权图的非负值的无圈图,解决单源最短路径的问题(即从st到ed的最短路径,st确定)。

    3、算法实现:

    (1)edge二维数组表示存储图的边的信息,(即邻接数组存储图结构),vis数组存储每个节点的状态

    dis存储每个节点到起始节点的距离,pre存储每个节点的前一个节点,用来记录最短路径。

    (2)开始先初始化,pre数组初始化为-1,设置dis[st]=1(这一步也可以放到dij()函数里面)。

    (3)找到dis中最小值的未被标记过的值,然后标记这个值,找到这个值的邻接点中可以更新的距离。

    (4)重复(3)直到pos为-1。

    4、代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    //建立图结构
    int edge[maxn][maxn],dis[maxn],pre[maxn];
    int vis[maxn],m,n;
    void Init()
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            edge[i][j]=(i==j?0:INF);
        for(int i=1;i<=n;i++) dis[i]=INF,pre[i]=-1,vis[i]=0;
    } 
    void dij(int st)
    {
        int i,j;
        dis[st]=0;
        for(j=1;j<=n;j++)
        {
            int pos=-1,mi=INF;
            for(i=1;i<=n;i++)
            if(vis[i]==0&&dis[i]<mi)
            {
                mi=dis[i];
                pos=i;
            }
            if(pos==-1) break;
            vis[pos]=1;
            for(i=1;i<=n;i++)
            if(vis[i]==0&&dis[pos]+edge[pos][i]<dis[i])
            {
                dis[i]=dis[pos]+edge[pos][i];
                pre[pos]=i;
            }
        }
    }
    void Print(int st,int ed)
    {
        int x=dis[st];
        printf("路径是:"); 
        while(st!=-1)
        {
            printf(" %d",st);
            st=pre[st];
        }
        printf("	最短路径距离是:%d
    ",dis[ed]);
    }
    int main(void)
    {
        int x,y,i,z;
        cin>>n>>m;
        Init();
        for(i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            edge[x][y]=edge[y][x]=z;
        }
        dij(1);
        Print(1,n);
        return 0;
    }
    /*
    5 5
    1 2 3
    1 4 5
    2 3 4
    3 4 1
    3 5 7
    */
    View Code

    5、时间复杂度:O(|V|^3)。

    补充:具有负值边的图

    1、可以加上一个值变为正数然后再进行dij。

    2、直接用广搜,队列可以保证不会重复计算。

    过程:

    (1)将开始的节点放进队列

    (2)每一次取出队列的头结点,并查找它的邻接节点,寻找比它小的边的权值。

    (3)重复操作直到队列为空。

    #include<iostream>
    #include<cstdio>
    #include<queue>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    int edge[maxn][maxn],vis[maxn],pre[maxn],dis[maxn],n,m;
    void Init()
    {
        int i,j;
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
            edge[i][j]=(i==j?0:INF);
        for(i=1;i<=n;i++) pre[i]=-1,dis[i]=INF,vis[i]=0;
        dis[1]=0;
    }
    void bfs()
    {
        queue <int> q;
        q.push(1);
        while(!q.empty())
        {
            int top=q.front();
            q.pop();
            
            for(int i=1;i<=n;i++)
            {
                if(edge[top][i]!=INF&&top!=i&&dis[top]+edge[top][i]<dis[i])
                {
                    dis[i]=dis[top]+edge[top][i];
                    pre[i]=top;
                    if(vis[i]==0) q.push(i),vis[i]=1;
                }
            }
            vis[top]=0;
        }
    }
    void Print(int st,int ed)
    {
        while(st!=-1)
        {
            printf("%d ",st);
            st=pre[st];
        }
        printf("	%d
    ",dis[ed]);
    }
    int main(void)
    {
        int i,j,x,y,z;
        cin>>n>>m;
        Init();
        for(i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            edge[x][y]=z;
        }
        bfs();
        Print(n,n);
        return 0;
    }
    /*
    5 5
    1 2 3
    2 3 -4
    3 5 7
    1 4 5
    4 3 -1
    */
    View Code

    三、Floyd算法

    1、思路:动态规划,状态转移方程dw=min(dw,Cv,w);

    2、适用范围:边权值可以为负数,可以求从任意节点s到其他节点的最短路径。

    3、算法实现:

    (1)设置二维数组dis(存储每个节点到其他节点的距离),path(记录i,j节点之间的中转节点)。

    (2)先初始化,dis数组赋值为INF,path赋值为j(为中转做准备)。

    (3)三层循环,k表示中转接点,i,j循环用来遍历图的每一个节点。

    (4)可以求出任意两点之间的最短距离。

    4、代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    int dis[maxn][maxn],path[maxn][maxn],m,n;
    void Init()
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) dis[i][j]=INF,path[i][j]=j;
    }
    void Floyd()
    {
        int i,j,k;
        for(k=1;k<=n;k++)
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)
                if(dis[i][j]>dis[i][k]+dis[k][j])
                dis[i][j]=dis[i][k]+dis[k][j],path[i][j]=path[i][k];
    }
    int main(void)
    {
        int x,y,z;
        cin>>n>>m;
        Init();
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y>>z;dis[x][y]=z;
        }
        Floyd();
        printf("%d
    ",dis[1][n]);
        int st=1,ed=n;
        while(st!=ed) //记录路径 
        {
            cout<<st<<" ";
            st=path[st][ed];
        }
        printf("%d
    ",ed);
        return 0;
    }
    /*
    5 4
    1 2 3
    2 3 4
    3 4 2
    2 5 -1
    */
    View Code

    5、复杂度:O(|V|^3)。

    四、Bellmen-Ford 算法

    1、思路:判断图中是否含有负环,如果松弛n-1次还可以松弛,说明有负环,无法得出结果,否则就完成。

    2、适用范围:含有或不含有负数边的图,求单源最短路径的算法。

    3、实现过程:

    (1)建立一个边的结构体数组edge(存储边的两个节点和权值,如果有向图,输入时就是一条边,无向图每次输入就是两条边);

    建立一个数组dis记录每个节点的距离到起始节点的距离;

    建立一个pre数组,存储每一个节点的前一个节点。

    (2)初始化:dis初始化为INF,起始节点初始化为0;pre数组初始化为0;

    (3)进行n-1次松弛操作,每次判断dis[v]<dis[u]+C(u,v)是否成立,成立的话,更新dis[v]和pre[v](类似于dijkstra算法里对每个节点的距离的更新)。

    (4)判断是否有负负数环。

    4、代码:(以有向图为例)

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    struct Node{
        int u,v,cost;
    }edge[maxn]; 
    int dis[maxn],pre[maxn],m,n,st,ed;
    void Init() //初始化 
    {
        for(int i=1;i<=n;i++) dis[i]=(i==st?0:INF),pre[i]=0; 
    }
    bool Bellman_Ford()
    {
        int i,j;
        for(i=1;i<=n-1;i++) //n-1次松弛 
            for(j=1;j<=m;j++)
            if(dis[edge[j].u]+edge[j].cost<dis[edge[j].v])
            dis[edge[j].v]=dis[edge[j].u]+edge[j].cost,pre[edge[j].v]=edge[j].u;
        for(i=1;i<=n;i++) //判断是否存在负数环 
        if(dis[edge[i].v]>dis[edge[i].u]+edge[i].cost) return false;
        return true;
    }
    int main(void)
    {
        int x,y,z,i;
        cin>>n>>m;
        st=1;ed=n;
        Init();
        for(i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            edge[i].u=x;edge[i].v=y;edge[i].cost=z;
        }
        pre[st]=st;
        if(Bellman_Ford())
        {
            printf("%d
    ",dis[ed]);
            while(ed!=pre[ed])
            {
                printf("%d ",ed);
                ed=pre[ed];
            }
            printf("%d
    ",ed);
        }
        return 0;
    }
    
    /*
    5 4
    1 2 3
    2 3 4
    3 4 2
    2 5 -1
    */
    View Code

    5、复杂度:O(|E|*|V|)。

    五、Bellman-Ford算法的队列优化(spfa算法)

    1、思路:一次松弛结束后会有一些节点的值已经达到最小,以后也不用去改变,如果再次判断就浪费了时间。可以考虑每次对最短路径发生变化的点进行松弛,这里就是对Bellman算法的优化(与上面具有负数边的广搜处理类似)。

    2、适用范围:含有或不含有负数边的图,求单源最短路径的算法。

    3、实现过程:

    (1)建立一个Node表示边的信息,建立一个数组dis表示距离的大小,vis数组标记每个节点是否出现过,pre存储路径,cnt存储每一个节点出现的次数。

    (2)初始化,清空edge,dis赋值为INF;cnt,pre,vis数组初始化为0.

    (3)用广搜的思想进行,将起始节点入队。

    (4)取出第一个节点,标记它出队,找到与它相邻的节点,更新相邻节点的距离的值,如果这个邻接点未在队列中,就可以入队。

    判断一下这个点的操作数是否超过n-1,如果超过n-1就返回false。

    (5)重复(4)直到队列为空或者直接返回false。

    4、代码:(以有向图为例)

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<vector>
    using namespace std;
    //以有向图为例。
    const int maxn = 1200;
    const int INF = 0x3ffff;
    int vis[maxn],dis[maxn],pre[maxn],cnt[maxn],n,m,st,ed;
    struct Node{
        int v,cost;
        Node(int a,int b):v(a),cost(b){}
    };
    vector <Node> edge[maxn];
    void Init()
    {
        for(int i=1;i<=n;i++) dis[i]=INF,edge[i].clear(),vis[i]=0,pre[i]=0,cnt[i]=0;
        st=1;ed=n;
    }
    bool Bellman()
    {
        dis[st]=0;
        queue <int> q;
        q.push(st);vis[st]=1;
        while(!q.empty())
        {
            int top=q.front();q.pop();
            vis[top]=0;
            for(int i=0;i<edge[top].size();i++)
            {
                Node tp=edge[top][i];
                if(dis[top]<INF&&dis[top]+tp.cost<dis[tp.v])
                {
                    dis[tp.v]=dis[top]+tp.cost;
                    pre[tp.v]=top;
                    if(vis[tp.v]==0)
                    {
                        q.push(tp.v);vis[tp.v]=1;
                        if(++cnt[tp.v]>n) return false;
                    }
                }
            }
        }
        return true;
    }
    int main(void)
    {
        int x,y,z;
        cin>>n>>m;
        Init();
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            edge[x].push_back(Node(y,z));
        }
        if(Bellman())
        {
            printf("%d
    ",dis[ed]);
            while(ed!=st)
            {
                cout<<" "<<ed;
                ed=pre[ed];
            }
            printf(" %d
    ",st);
        }
        return 0;
    } 
    
    /*
    5 4
    1 2 3
    2 3 4
    3 4 2
    2 5 -1
    */
    View Code

    5、复杂度:O(|V|*|E|)。

  • 相关阅读:
    ReLU激活函数:简单之美
    逻辑回归(Logistic Regression)推导
    机器学习之视频推荐
    TensorFlow OOM when allocating tensor with shape[5000,384707]
    工银亚洲账户激活
    微信小程序之多行文本省略号
    关于微信小程序:scroll-view,backgroundTextStyle
    css之box-sizing属性(css3中的新属性)
    css之max-width属性
    css中的display属性之li元素
  • 原文地址:https://www.cnblogs.com/2018zxy/p/10120549.html
Copyright © 2020-2023  润新知