现在的位置: 首页 > 综合 > 正文

最短路径算法合集

2014年08月17日 ⁄ 综合 ⁄ 共 6902字 ⁄ 字号 评论关闭

今天是寒假的最后一天,不知道干点什么事情才更有意义,于是想到把自己看过的图论的算法写一下吧!就算做是给别人的一个参考和自己的一个总结。

为了节省时间,不对该问题做太细致的介绍,也就是说这篇日志适合于已经基本了解最短路径算法的读者来看。

1、Dijkstra(迪杰斯克拉)算法

适用问题:该算法适用于解决

①、单源最短路径:已知图G=(V,E),我们希望找出从给定某结点S∈V到V中每个结点的最短路径。

②、边权非负

该算法的主要思想是:

①、设定一个集合Set,对任意结点t∈V,若已知t到s的最短路径,则将 t 放入Set。

②、到s最近的那个结点肯定和s有边直接相连,不妨设该点为p。p到s所走的最短路径必然是走p和s相连的那条边

③、到s次近的那个结点q如何到s呢?要么直接走q与s相连的那条边,要么先到②中的p,然后到s

④、到s的最短距离是第三长的那个,如何到s呢?无非有三种情况:直接到s,先到p然后到s,先到q然后走q到s走的最短路径

⑤、其他的结点类比②③④可知

算法代码实现:

①、图用邻接矩阵表示:(O(n^2))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 20;
//node numbers start from 1
int graph[maxn][maxn];
int m, n;//the numbers of edges and nodes
bool Set[maxn];
int dis[maxn];
int pre[maxn];
void Dijkstra(int s)
{
    //initialize
    for(int i = 1; i <= n; i ++)
    {
        Set[i] = (i == s ? true : false);
        dis[i] = (i == s ? 0 : graph[s][i]);
        pre[i] = (i == s ? 0 : s);
    }

    for(int i = 0; i < n - 1; i ++)
    {
        int min = INF, minn;
        for(int j = 1; j <= n; j ++) if(!Set[j])
        {
            if(min >= dis[j])
            {
                min = dis[j]; minn = j;
            }
        }
        Set[minn] = true;
        for(int j = 1; j <= n; j ++) if(!Set[j])
        {
            if(dis[j] > min + graph[minn][j])
            {
                dis[j] = min + graph[minn][j];
                pre[j] = minn;
            }
        }
    }
}
void print_path(int v)
{
    if(!v) return ;
    print_path(pre[v]);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r", stdin);
    scanf("%d%d", &m, &n);

    //initialize the graph
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            graph[i][j] = (i == j ? 0 : INF);

    //input the graph
    for(int i = 0; i < m; i ++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        graph[u][v] = w;
    }

    //call the dijkstra algorithm
    Dijkstra(1);

    //output the result
    for(int i = 1; i <= n; i ++)
    {
        print_path(i);
        printf("\n%d\n", dis[i]);
    }
    return 0;
}

②、图用邻接表表示(适用于图中有重边的情况)(O(n^2))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
using namespace std;
const int maxn = 20;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int from, to, weight;
};
vector<Edge> edges;
//the nodes numbers start from 1
vector<int> G[maxn];//G[i][j] means the j-th edge of node i
int n, m;
int Set[maxn], dis[maxn], pre[maxn];

void Dijkstra(int s)
{
    for(int i = 1; i <= n; i ++)
    {
        Set[i] = false;
        dis[i] = (i == s ? 0 : INF);
        pre[i] = (i == s ? 0 : s);
    }
    for(int i = 0; i < n; i ++)
    {
        int min = 0;
        for(int j = 1; j <= n; j ++) if(!Set[j])
        {
            if(min == 0) min = j;
            else if(dis[j] < dis[min]) min = j;
        }
        Set[min] = true;
        for(int j = 0; j < G[min].size(); j ++)
        {
            int v = edges[G[min][j]].to;
            int w = edges[G[min][j]].weight;
            if(dis[v] > dis[min] + w)
                dis[v] = dis[min] + w;
        }
    }
}
void print_path(int v)
{
    if(!v) return;
    print_path(pre[v]);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r", stdin);
    scanf("%d%d", &m, &n);
    //input the graph
    while(m --)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        edges.push_back((Edge){u, v, w});
        G[u].push_back(edges.size() - 1);
    }

    Dijkstra(1);

    for(int i = 1; i <= n; i ++)
    {
        print_path(i);
        printf("\n%d\n", dis[i]);
    }
    return 0;
}

③、用优先队列优化后的实现代码(O(mlogn))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 20;
const int INF = 0x3f3f3f3f;
struct Edge
{
        int from, to, weight;
};
vector<Edge> edges;
vector<int> G[maxn];
int m, n;
int Set[maxn], dis[maxn], pre[maxn];
void Dijkstra(int s)
{
    for(int i = 1; i <= n; i ++)
    {
        dis[i] = (i == s ? 0 : INF);
        Set[i] = false;
        pre[i] = (i == s ? 0 : s);
    }

    typedef pair<int, int> pii;
    priority_queue<pii, vector<pii>, greater<pii> > q;
    q.push(make_pair(dis[s], s));
    while(!q.empty())
    {
        pii u = q.top(); q.pop();
        int x = u.second;
        if(Set[x]) continue;
        Set[x] = 1;
        for(int i = 0; i < G[x].size(); i ++)
        {
            int v = edges[G[x][i]].to;
            int w = edges[G[x][i]].weight;
            if(dis[v] > dis[x] + w)
            {
                dis[v] = dis[x] + w;
                q.push(make_pair(dis[v], v));
                pre[v] = x;
            }
        }
    }
}
void print_path(int v)
{
    if(!v) return;
    print_path(pre[v]);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r", stdin);
    //input the graph
    scanf("%d%d", &m, &n);
    while(m --)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        edges.push_back((Edge){u, v, w});
        G[u].push_back(edges.size() - 1);
    }

    Dijkstra(1);

    for(int i = 1; i <= n; i ++)
    {
        print_path(i);
        printf("\n%d\n", dis[i]);
    }
    return 0;
}

2、Bellman-Ford(贝尔曼-福特)算法

适用问题:该算法适用时需满足一下条件

①、单源最短路径

②、边权取值正负都可以,但是图中不能包含权值和为负数的圈

算法的主要思想:

①、注意到:最短路径的长度最大为n-1

②、定义状态d(i,j,k)代表结点i到结点j最多经过k条边的最短距离

③、状态转移方程d(i, j, k) = min{d(i, q, k - 1) + e(q,i) | e(q, i)
 E}

实现代码:

①、图用邻接表表示(O(nm)),如果用邻接矩阵的话效率会变低

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 20;
const int INF = 0x3f3f3f3f;
struct Edge
{
  int from, to, weight;
};
vector<Edge> edges;
vector<int> G[maxn];
int m, n;
int dis[maxn], pre[maxn];

void Bellman_Ford(int s)
{
    for(int i = 1; i <= n; i ++)
    {
        dis[i] = (i == s ? 0 : INF);
        pre[i] = (i == s ? 0 : s);
    }
    for(int k = 0; k < n - 1; k ++)
    {
        for(int i = 0; i < edges.size(); i ++)
        {
            int u = edges[i].from, v = edges[i].to, w = edges[i].weight;
            if(dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                pre[v] = u;
            }
        }
    }
}

void print_path(int v)
{
    if(!v) return;
    print_path(pre[v]);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r", stdin);
    //input the graph
    scanf("%d%d", &m, &n);
    while(m --)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        edges.push_back((Edge){u, v, w});
        G[u].push_back(edges.size() - 1);
    }

    Bellman_Ford(1);

    for(int i = 1; i <= n; i ++)
    {
        print_path(i);
        printf("\n%d\n", dis[i]);
    }
    return 0;
}

②、用FIFO优化之后,最坏时还是O(nm),不过一般效率要好些

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 20;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int from, to, weight;
};
vector<Edge> edges;
vector<int> G[maxn];
int m, n;
int dis[maxn], pre[maxn];

void Bellman_Ford(int s)
{
    for(int i = 1; i <= n; i ++)
    {
        dis[i] = (i == s ? 0 : INF);
        pre[i] = (i == s ? 0 : s);
    }

    queue<int> q;
    q.push(s);
    bool inq[maxn];
    memset(inq, false, sizeof(inq));
    inq[s] = true;

    while(!q.empty())
    {
        int x = q.front(); q.pop();
        inq[x] = false;
        for(int i = 0; i < G[x].size(); i ++)
        {
            int u = edges[G[x][i]].from, v = edges[G[x][i]].to, w = edges[G[x][i]].weight;
            if(dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                pre[v] = u;
                if(!inq[v])
                {
                    q.push(v);
                    inq[v] = true;
                }
            }
        }
    }
}

void print_path(int v)
{
    if(!v) return;
    print_path(pre[v]);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r", stdin);
    scanf("%d%d", &m, &n);
    while(m --)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        edges.push_back((Edge){u, v, w});
        G[u].push_back(edges.size() - 1);
    }

    Bellman_Ford(1);

    for(int i = 1; i <= n; i ++)
    {
        print_path(i);
        printf("\n%d\n", dis[i]);
    }
    return 0;
}

3、Floyd(佛洛依德算法)

使用条件:

①、所有顶点之间的最短路径

②、边权即可为正,也可为负

主要思想:

Floyd算法的主要思想和Bellman_Ford算法有异曲同工之妙,都是基于递推的(我感觉就是动态规划)

①、注意到对于任意一条最短路径,该条路径所经过的结点的编号不会超出1~n这n种可能。

②、设状态d(i, j, k)表示结点i到结点j的满足某个特定条件的最短路径的长度,该条件为:最短路径上出去起始点外的结点的编号最大不会超过k

③、状态转移方程为:d(i, j, k) = min{d(i, j, k - 1), d(i, k, k - 1) + d(k, j, k - 1)}

实现代码:

图用邻接矩阵表示, 算法的复杂度为O(n^3),代码写起来也比较简单,我感觉原因就是状态设计的好

#include<cstdio>
#include<cstring>
#include<cstdlib>
const int maxn = 20;
const int INF = 0x3f3f3f3f;
//the node numbers start from 1
int m, n;
int dis[maxn][maxn];//dis[i][j] means the distance of the shortest path from node i to node j
int pre[maxn][maxn];// pre[i][j] means the previous node of node i on the shortest path which from i to j
void print_path(int v, int s)
{
    if(!v) return ;
    print_path(pre[v][s], s);
    printf("%d  ", v);
}
int main(void)
{
    freopen("f:\\code\\file\\data.txt", "r",stdin);
    scanf("%d%d", &m, &n);
    //initialize
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j++)
        {
            dis[i][j] = (i == j ? 0 : INF);
            pre[i][j] = (i == j ? 0 : j);
        }
    }
    while(m --)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        dis[u][v] = w;
    }

    //recurence
    for(int k = 1; k <= n; k ++)
        for(int i = 1; i <= n; i ++)
             for(int j = 1; j <= n; j ++)
                if(dis[i][j] > dis[i][k] + dis[k][j])
                {
                    dis[i][j] = dis[i][k] + dis[k][j];
                    pre[i][j] = pre[i][k];
                }
    // output the paths
     for(int i = 1; i <= n; i ++)
     {
         print_path(i, 1);
         printf("\n%d\n", dis[i][1]);
     }
    return 0;
}

抱歉!评论已关闭.