最短路

Dijkstra算法

朴素的Dijkstra算法和prim算法在代码上惊人的相似。

而Dijkstra算法的思想是:在待扩展边中取出最小边,利用此边进行松弛操作,然后更新待扩展边集。重复以上步骤直到所有结点都已访问过。

Dijkstra算法只对没有负权回路的图有效,当然对带负权边,但没有负权回路的图依然有效。

for (int i=1;i<n;i++)
{
    minn=INF;
    for (int j=1;j<=n;j++)
    if (!vis[j] && d[j]<minn)
        minn=d[k=j];
    vis[k]=true;
    for (int j=1;j<=n;j++)
        d[j]=min(d[j],d[k]+map[k][j]);
}

明显朴素的Dijkstra算法耗时O(N^2),时间主要浪费在每次循环寻找待扩展边中的最小边,利用C++的STL提供的方便,使用优先队列进行优化,当然图的存储方法只能改为使用边表存储,否则松弛操作一样耗时巨大。下面的代码写得一般,有点长了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

struct edge{     //存储边
    int v,w;
    edge() { v=w=0; }
    edge(int x,int y) { v=x; w=y; }
};
vector<edge> a[100];

struct node{         //存储待扩展边的数据,当然也可以直接利用存储边的结构体,但是为了直观就分开写了
    int dd,w;     //dd存储结点,w存储边权
    node() {dd=w=0; };
    node(int x,int y) { dd=x;w=y; }
    bool operator < (const node &x) const      //优先队列比较函数
    {
        if (w==x.w) return dd<x.dd;
        else return w<x.w;
    }
};

int d[100];
int dis[100];

void dijkstra(int S)
{
    priority_queue<node> p;
    memset(dis,10,sizeof(dis));
    dis[S]=0;
    p.push(node(S,dis[S]));     //把start点压入队列
    while (!p.empty())
    {
        node u=p.top(); p.pop();    //弹出待扩展边的最小边
        for (int i=0;i<d[u.dd];i++)    //松弛操作
        {
            edge e=a[u.dd][i];
            if (dis[e.v]>u.w+e.w)
            {
                dis[e.v]=u.w+e.w;
                p.push(node(e.v,dis[e.v]));  //待扩展边进队
            }
        }
    }
}

int main()
{
    for (int i=0;i<100;i++) a[i].clear();
    memset(d,0,sizeof(d));

    int n,m,u,v,w;
    cin>>n>>m;
    for (int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        a[u].push_back(edge(v,w));
        d[u]++;
    }

    dijkstra(1);

    for (int i=1;i<=n;i++)
        cout<<"1->"<<i<<":"<<dis[i]<<endl;
    return 0;
}

Bellman-Ford算法

朴素的bellman-ford算法说死了就是一个笨方法,就是通过不断地对每一条边进行松弛操作,耗时O(VE),但是经过一个简单的优化后效率大大提升,听说效率比Dijkstra好多了。

这里直接给出经过优化的bellman-ford算法。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

#define NN 105

struct edge{
    int u,v,w;
    edge() {u=v=w=0;}
    edge(int x,int y,int z) {u=x;v=y;w=z;}
};

vector <edge> e;
int n;
int dis[NN];

bool relax(int u,int v,int w)  //松弛操作
{
    if (dis[u]+w<dis[v])
    {
        dis[v]=dis[u]+w;
        return true;
    }
    return false;
}

bool Bell(int S)
{
    memset(dis,10,sizeof(dis));
    dis[S]=0;

    bool flag=false;
    int count=0;
    while (!flag && ++count<n)  //注意flag的变化,其实flag就是优化,不用flag则运行n-1次也会停下来
    {
        for (int i=0;i<e.size();i++)
            flag=flag||relax(e[i].u,e[i].v,e[i].w);  //如果没有进行过松弛操作,那么就没必要在做下去了
        flag=!flag;
    }

    for (int i=0;i<e.size();i++)   //负权回路的判断
        if (relax(e[i].u,e[i].v,e[i].w))
            return false;
    return true;
}

int main()
{
    int m,u,v,w;
    cin>>n>>m;
    e.clear();
    for (int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        e.push_back(edge(u,v,w));
        e.push_back(edge(v,u,w));
    }

    bool ff=Bell(1);
    if (!ff)
        cout<<"no"<<endl;
    else for (int i=2;i<=n;i++)
            cout<<dis[i]<<endl;
    return 0;
}

SPFA算法

SPFA算法是西南交大段凡丁发表的,在bellman-ford的基础上加入了队列优化,而在SPFA算法上又可以加入SLF和LLL优化,使得整个算法的效率非常可观。

算法的思想是从队列中取出点进行松弛操作,若松弛成功则加入队列,直到队列为空。而为了处理负权回路,需要加入一个数组记录结点入队的次数,若一个结点入队次数大于n,则可以判断出现了负权回路。

这里并没有加入SLF和LLL优化。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

#define NN 1000

struct edge{
    int v,w;
    edge() {v=w=0;}
    edge(int x,int y) {v=x;w=y;}
};

vector<edge> e[NN];
int dis[NN];
int n;

bool relax(int u,int v,int w)    //松弛操作
{
    if (dis[u]+w<dis[v])
    {
        dis[v]=dis[u]+w;
        return true;
    }
    return false;
}

bool SPFA(int S)
{
    memset(dis,10,sizeof(dis));
    dis[S]=0;

    int count[NN]={0};    //统计入队次数
    count[S]=1;

    queue<int> q;
    q.push(S);

    bool f[NN]={0};    //标记是否在队列中,避免重复进队
    f[S]=true;
    while (!q.empty())
    {
        int u=q.front();
        for (int i=0;i<e[u].size();i++)
        {
            int v=e[u][i].v;
            if (count[v]>=n) return false;
            if (!f[v] && relax(u,v,e[u][i].w))
            {
                count[v]++;
                q.push(v);
                f[v]=true;
            }
        }
        q.pop();
        f[u]=false;
    }
    return true;
}

int main()
{
    int m,u,v,w;
    cin>>n>>m;
    for (int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        e[u].push_back(edge(v,w));
        e[v].push_back(edge(u,w));  //无向图
    }

    bool ff=SPFA(1);

    if (!ff) cout<<"no"<<endl;
    else for (int i=2;i<=n;i++)
            cout<<dis[i]<<endl;
    return 0;
}

Floyd算法

Floyd算法最简单但是速度不敢恭维。

思想:对每个节点不断进行松弛操作,到最后得到的结果就是多源最短路的结果了。

for (int k=1;k<=n;k++)
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            map[i][j]=min(map[i][j],map[i][k]+map[k][j]);
原文地址:https://www.cnblogs.com/ay27/p/2811821.html