最短路径算法总结(floyd,dijkstra,bellman-ford)

继续复习数据结构和算法,总结一下求解最短路径的一些算法。ios

弗洛伊德(floyd)算法

弗洛伊德算法是最容易理解的最短路径算法,能够求图中任意两点间的最短距离,但时间复杂度高达\(O(n^3)\),主要思想就是若是想缩短从一个点到另外一个点的距离,就必须借助一个中间点进行中转,好比A点到B点借助C点中转的话AB的距离就能够更新为\(D(a,b)=Min(D(a,b),D(a,c)+D(c,b))\),这样咱们用每个结点做为中转结点,尝试对另每两个结点进行距离更新,总共须要三层循环进行遍历。c++

核心代码以下,图存储在邻接矩阵G中。算法

for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            for (int k = 0; k < n; k++)
            {
                G[j][k] = min(G[j][k], G[j][i] + G[i][k]);
            }
        }
    }

迪杰斯特拉(Dijkstra)算法

迪杰斯特拉算法是一种求解单源最短路径的算法,给定一个结点,能够求出图上各个结点到该结点最短距离。数组

没学过的话推荐看看这个视频:https://www.bilibili.com/video/av21376839?p=13,从8分钟开始。看完以后基本上就明白了Dijkstra算法的运行过程。总结一下就是不断寻找离源点最近的点并将其做为新的源点去更新其余点到目标点的距离。数据结构

代码以下,nowIndex表明当前源点编号,minDis是当前源点到其余点的最短距离,用于选择下一个源点,dis数组存储每一个点到最终目标点的距离,也就是结果,mark数组用于标记结点是否被看成源点过。数据结构和算法

#include<iostream>
#include<algorithm>
using namespace std;

#define inf 100000000

int G[10][10];
int dis[10];
bool mark[10];
int n, m;

void dijkstra(int nowIndex)
{
    mark[nowIndex] = true;
    for (int i = 1; i <= n; i++)//先将跟源点直接相连的结点更新一遍
        dis[i] = min(dis[i], G[nowIndex][i]);
    for (int i = 1; i < n; i++)//循环n-1次,由于源点已经更新过了
    {
        int minDis = inf;
        for (int j = 1; j <= n; j++)//找离当前源点最近的点
        {
            if (!mark[j] && dis[j] < minDis)
            {
                minDis = dis[j];
                nowIndex = j;
            }
        }
        mark[nowIndex] = true;
        for (int j = 1; j <= n; j++)//用当前源点去更新
            dis[j] = min(dis[j], dis[nowIndex] + G[nowIndex][j]);
    }
}

int main()
{
    cin >> n >> m;//输入顶点数和边数
    int u, v, w;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i != j)
                G[i][j] = inf;
            else
                G[i][j] = 0;
    for (int i = 1; i <= n; i++)
        dis[i] = inf;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;//输入无向边
        G[u][v] = w;
        G[v][u] = w;
    }
    dijkstra(1);//以1号结点为源点
    for (int i = 1; i <= n; i++)
    {
        cout << dis[i] << ' ';
    }

    return 0;
}

邻接表实现

使用邻接表存储图能大大下降空间复杂度,代码以下:ide

#include<iostream>
#include<algorithm>
using namespace std;

#define inf 100000000
#define maxN 10000

int value[maxN], to[maxN], nextL[maxN];
int head[maxN], total;
int dis[maxN];
bool mark[maxN];
int n, m;

void dijkstra(int nowIndex)
{
    for (int i = 0; i <= n; i++)dis[i] = inf;
    dis[nowIndex] = 0;
    mark[nowIndex] = true;
    for (int i = head[nowIndex]; i; i = nextL[i])
    {
        dis[to[i]] = min(dis[to[i]], dis[nowIndex] + value[i]);
    }
    for (int i = 1; i < n; i++)//循环n-1次,由于源点已经更新过了
    {
        int minDis = inf;
        for (int j = 1; j <= n; j++)//找离当前源点最近的点
        {
            if (!mark[j] && dis[j] < minDis)
            {
                minDis = dis[j];
                nowIndex = j;
            }
        }
        mark[nowIndex] = true;
        for (int j = head[nowIndex]; j; j = nextL[j])
        {
            dis[to[j]] = min(dis[to[j]], dis[nowIndex] + value[j]);
        }
    }
}

void AddLine(int a, int b, int c)
{
    total++;
    to[total] = b;
    value[total] = c;
    nextL[total] = head[a];
    head[a] = total;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        AddLine(a, b, c);
    }
    dijkstra(1);
    for (int i = 1; i <= n; i++)
        cout << dis[i] << " ";

    return 0;
}

堆优化

普通的Dijkstra时间复杂度为\(O(n^2)\),但能够经过优化达到\(O(nlogn)\),注意在上面的循环中咱们每次都要取出离当前源点最近的点,因此能够用优先级队列来优化。每次搜索将修改过dis的点进队,而后每次取队首就是最近的点。优化

代码以下:spa

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

#define inf 100000000
#define maxN 10010

int s;
int value[500001], to[500001], nextL[500001];
int head[maxN], total;
int dis[maxN];
bool mark[maxN];
int n, m;

typedef pair<int, int> disID;
priority_queue<disID,vector<disID>,greater<disID>> q;

void dijkstra(int nowIndex)
{
    for (int i = 0; i <= n; i++)dis[i] = inf;
    dis[nowIndex] = 0;
    q.push(disID(0, nowIndex));
    while (!q.empty())
    {
        int t = q.top().second;
        q.pop();
        if (mark[t])continue;
        mark[t] = true;
        for (int i = head[t]; i ; i=nextL[i])
        {
            if (dis[to[i]] > dis[t] + value[i])
            {
                dis[to[i]] = dis[t] + value[i];
                q.push(disID(dis[to[i]], to[i]));
            }
        }
    }
}

void AddLine(int a, int b, int c)
{
    total++;
    to[total] = b;
    value[total] = c;
    nextL[total] = head[a];
    head[a] = total;
}

int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        AddLine(a, b, c);
    }
    dijkstra(s);
    for (int i = 1; i <= n; i++)
    {
        cout << dis[i] << ' ';
    }

    return 0;
}

Bellman-ford算法

上面的Dijkstra算法存在一个问题就是不能处理存在负权边的状况,只要有边的权值是负数就不能用,这时能够用Bellman-ford算法解决。code

Bellman-ford算法的思想是这样的,咱们将每条边的起点、权值、终点存储为三个数组from[i],val[i],to[i],而后扫描每一条边,看能不能经过走这条边来使dis[to[i]]减小。

代码很简单以下:

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

#define inf 100000000
int from[10000], val[10000], to[10000];
int dis[10000];
int n, m;

void Bellman_ford(int u)
{
    for (int i = 0; i <= n; i++)dis[i] = inf;
    dis[u] = 0;
    while (true)
    {
        bool update = false;
        for (int i = 1; i <= m; i++)
        {
            if (dis[from[i]] != inf && dis[to[i]] > dis[from[i]] + val[i])
            {
                dis[to[i]] = dis[from[i]] + val[i];//更新
                update = true;
            }
        }
        if (!update)break;//直到每一条边都不能使dis减小
    }
}

int main()
{
    cin >> n >> m ;
    for (int i = 1; i <= m; i++)
    {
        cin >> from[i] >> to[i] >> val[i];
    }
    Bellman_ford(1);
    for (int i = 1; i <= n; i++)
        cout << dis[i] << ' ';
    return 0;
}

算法中的while循环最多循环n-1次,因此Bellman-ford的时间复杂度是\(O(mn)\),不只能处理负权边,并且在稀疏图(顶点数远多于边数)当中比Dijkstra快。

相关文章
相关标签/搜索