图论四:最短路径算法

一、广度优先搜索

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("\t最短路径距离是:%d\n",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("\t%d\n",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]=dis[y][z]=z;
    }
    Floyd();
    printf("%d\n",dis[1][n]);
    int st=1,ed=n;
    while(st!=ed) //记录路径 
    {
        cout<<st<<" ";
        st=path[st][ed];
    }
    printf("%d\n",ed);
    return 0;
}
/*
5 4
1 2 3
2 3 4
3 4 2
2 5 -1
*/
View Code

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

猜你喜欢

转载自www.cnblogs.com/2018zxy/p/10120549.html