图的路径:最短路-四种算法

       先说前三种,主要解决的问题是:“求a点到b点的最短路”,解决方法是:用以下三种算法中的一种,求出a点到其他所有点的最短路,即可知道a点到b点的最短路。

       前三种最短路算法是:Dijkstra(及优先队列优化),Bellman-Ford,SPFA(实际上就是BF的优化版)。


Dijkstra算法: (要求边权只能大于等于0)

未优化 O(N2):

       贪心算法:每次找到当前距离起始点(a点)最短的点扩展更新,且该点不会再被更新

       数据结构:开一个二维数组cost[i][j]来存储各点之间的距离(邻接矩阵),INF表示无通路,x点到x点之间距离为0;用一维数组lowcost[i]表示最短路径,用vis[i]保存路径中某点是否已经走过。

Dij+邻接矩阵 模板如下:

const int INF=10e7;
const int MAXN=1010;
int k,minn;
int cost[MAXN][MAXN];
int lowcost[MAXN];
bool vis[MAXN];

void dij(int n,int start)
{
    for(int i=1;i<=n;i++)
        lowcost[i]=INF,vis[i]=0;
    lowcost[start]=0;

    for(int j=1;j<=n;j++)
    {
        k=-1,minn=INF;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i]&&lowcost[i]<minn)
                {minn=lowcost[i];k=i;}
        }
        if(k==-1) break; //这里需要对k进行判定,是因为有可能在k未赋值的情况下使用了vis[k]
        vis[k]=1;
        for(int i=1;i<=n;i++)
            if(!vis[i]&&cost[k][i]>=0&&lowcost[k]+cost[k][i]<lowcost[i])
                lowcost[i]=lowcost[k]+cost[k][i];
    }
}
 


优先队列优化 O(NlogN):    //且对于稠密图,使用优先队列优化的Dijkstra算法更有效

       将原先未优化版的第一个O(N)优化,用优先队列来维护,复杂度变为O(logN),故总复杂度为O(NlogN)。

Dij+邻接表+优先队列 模板如下:

const int MAXN=1010;
const int INF=10e8;

struct node
{
    int v,val;
    node(int _v=0,int _val=0): v(_v),val(_val) {}
    //等价于node(int _v,int _val) {v=_v;val=_val;}
    bool operator < (const node &a) const
    {
        if(val==a.val) return v<a.v;
        else return val>a.val;
    }
};

vector<node> E[MAXN];
bool vis[MAXN];

void Dijkstra(int lowcost[],int n,int start)
{
    int len,u,v,cost;
    node qtemp;
    for(int i=1;i<=n;i++)
        lowcost[i]=INF;vis[i]=0;
    lowcost[start]=0;
    //用优先队列优化
    priority_queue<node> que;
    que.push(node(start,0));
    while(!que.empty())  //类似于BFS
    {
        qtemp=que.top();que.pop();
        u=qtemp.v;
        if(vis[u])
            continue;
        vis[u]=1;len=E[u].size();
        
        for(int i=0;i<len;i++)
        {
            v=E[u][i].v;cost=E[u][i].cost;
            if(!vis[v]&&lowcost[v]>lowcost[u]+cost)
            {
                lowcost[v]=lowcost[u]+cost;
                que.push(node(v,lowcost[v]));
            }
        }
    }
}


Bellman-Ford算法(边权可以为负权,且可用于判断图是否存在负权回路)

    原理是进行松弛操作,最多N-1次,复杂度O(NM)。(松弛一次的复杂度为O(M))

步骤:

       1.初始化所有点,每个点保存一个值,表示从原点到此点的距离,INF表示两点之间不连通;

       2.进行循环,下标从1到n-1(n为图中点的个数),复杂度O(N),在循环内部遍历所有的边,进行松弛,复杂度O(M);

       3.跳出循环后,遍历途中所有的边(edge(u,v)),判断是否存在:

     lowcost(v)>lowcost(u)+cost(u,v)的情况,若存在,则返回false,表示图中存在负权回路,无法求得最短路径。

BF+邻接表 模板如下:

const int MAXN=1010;
const int INF=10e8;

struct edge
{
    int u,v,cost;
    edge(int _u=0,int _v=0,int _cost=0) u(_u),v(_v),cost(_cost) {}
};

vector<edge> E;

bool Bellman_Ford(int lowcost[],int n,int start)
{ //返回值用于判断是否有负环,false说明有负环,即无法求出最短路
    bool ok;
    int u,v,cost;
    int len=E.size();
    
    for(int i=1;i<=n;i++)
        lowcost[i]=INF;
    lowcost[start]=0;
    
    for(int i=0;i<n-1;i++)
    {
        ok=0;
        for(int j=0;j<len;j++)
        {
            u=E[j].u;v=E[j].v;cost=E[j].cost;
            if(lowcost[v]>lowcost[u]+cost)  //进行松弛
            {
                lowcost[v]=lowcost[u]+cost;
                ok=1;
            }
        }
        if(!ok)
            return true;
    }
    for(int j=0;j<len;j++)
        if(lowcost[E[j].v]>lowcost[E[j].u]+E[j].cost)
            return false;
    return true;
}

//加边操作
inline void add_edge(int u,int v,int c)
{
    E.push_back(edge(u,v,c));
}


SPFA算法:

    是优化后的BF算法,在进行松弛的时候,每次只对上一次改变过的点进行松弛。

SPFA+邻接表 模板如下:

const int MAXN=1010;
const int INF=10e8;

struct edge
{
    int v,cost;
    edge(int _v=0,int _cost=0) v(_v),cost(_cost) {}
};

vector<edge> E[MAXN];
bool vis[MAXN];
int couNode[MAXN];

bool SPFA(int lowcost[],int n,int start)
{ //返回值用于判断是否有负环,false说明有负环,即无法求出最短路
    queue<int> que;
    int u,v,cost;
    int len=E.size();
    
    for(int i=1;i<=n;i++)
        lowcost[i]=INF,vis[i]=0,couNode[i]=0;
    lowcost[start]=0;vis[start]=0;couNode[start]=1;
    que.push(start);
    
    for(!que.empty())
    {
        u=que.front();que.pop();
        vis[u]=0;len=E[u].size();
        
        for(int i=0;i<len;i++)
        {
            v=E[i].v;cost=E[i].cost;
            if(lowcost[v]>lowcost[u]+cost)  //进行松弛
            {
                lowcost[v]=lowcost[u]+cost;
                if(!vis[v])
                {
                    vis[v]=1;
                    ++couNode[v];
                    que.push(v);
                    if(couNode[v]>n)
                        return false;
                }
            }
        }
    }
    return true;
}

//加边操作
inline void add_edge(int u,int v,int c)
{
    E[u].push_back(edge(v,c));
}


Floyd算法:

      第四种比较特殊又好用的算法是folyd算法;

      若要求图上任意两点之间的最短路(而不是给了确切的起点a),就用Folyd算法;此算法代码短,复杂度O(N3)用的是动态规划思想。

      且Floyd算法也可用于判环,若lowcost[a][a]有值且不为INF,那么说明图中存在从a点到a点的一个环。

Floyd+邻接矩阵 模板如下:

const int INF=10e7;
const int MAXN=1010;
int lowcost[MAXN][MAXN];  //记录a点到b点之间的最短路

void floyd(int n)
{
    for(int k=1;k<=n;k++)  //千万注意k要在最外层循环,否则会出错
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                lowcost[i][j]=min(lowcost[i][j],lowcost[i][k]+lowcost[k][j]);
}
原文地址:https://www.cnblogs.com/atmacmer/p/5177823.html