博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
有向无环图(DAG)的最短路径问题(拓扑排序)
阅读量:4089 次
发布时间:2019-05-25

本文共 3327 字,大约阅读时间需要 11 分钟。

问题

给定一个有向无环图和一个源点,求从该源点到其他所有的顶点的最短路径。我们已经知道了如何通过Dijkstra算法在非负权图中找到最短路径。即使图中有负权边,我们也知道通过Bellman-Ford算法找到一个从给定的源点到其它所有节点的最短路径。现在我们将看到一个在线性时间内运行得更快的算法,它可以在有向无环图中找到从一个给定的源点到其它所有可达顶点的最短路径。基本思想是利用拓扑排序。

分析

关于有向无环图(DAG)我们首先要知道,它们可以很容易地进行拓扑排序。拓扑排序应用范围非常之广,其中最常用的或许就是用于安排依赖任务(依赖任务是同属于一个工作中相同任务的实体,这些实体是保证互连的,它们解决共同的问题)。

下图即为对一个有向图的:

 

拓扑排序通常是用来“排序”依赖任务的!

经过拓扑排序,我们最终会得到一张DAG图的顶点列表,我们确信在拓扑排序列表中如果存在一条边(u,v),那么顶点u会先于顶点v进入列表中。

 

 

如果有一条边(u,v),那么顶点u一定在顶点v前面。这个结果通过这张图片变得更加通俗易懂。其中B和D之间没有边,但在列表中B在D前面!

此信息异常重要,我们唯一需要做的事情就是通过这个排序列表来计算距离最短的路径,这和Dijkstra算法比较相似。

好了,让我们来总结一下这个算法:

-首先,我们必须对有向无环图进行拓扑排序;

-其次,我们将到源点的距离初始化为0并将到其它所有顶点的距离设置为无穷大;

-最后,对于列表中的每一个顶点,我们从它的所有邻节点中找到最短路径的那个顶点;

这很像Dijkstra算法,但是其主要区别是我们使用的是经过拓扑排序的列表。

以下的图从这里的英文讲义拿来的:http://www.utdallas.edu/~sizheng/CS4349.d/l-notes.d/L17.pdf。演示了逐步找到最短路径的过程:

 

伪代码描述如下:

1) Initialize dist[] = {INF, INF, ….} and dist[s] = 0 where s is the source vertex.

2) Create a toplogical order of all vertices.
3) Do following for every vertex u in topological order.
………..Do following for every adjacent vertex v of u
………………if (dist[v] > dist[u] + weight(u, v))
………………………dist[v] = dist[u] + weight(u, v)

C++代码实现如下:

#include<iostream>

#include <list>

#include <stack>

#include <limits.h>

#define INF INT_MAX

using namespace std;

 

// 邻接表节点

class AdjListNode

{

    int v;

    int weight;

public:

    AdjListNode(int _v, int _w)  { v = _v;  weight = _w;}

    int getV()       {  return v;  }

    int getWeight()  {  return weight; }

};

 

// 图

class Graph

{

    int V;    // 顶点个数

 

    list<AdjListNode> *adj;

 

    void topologicalSortRecall(int v, bool visited[], stack<int> &stk);

public:

    Graph(int V);

 

    void addEdge(int u, int v, int weight);

 

    void shortestPath(int s);

};

 

Graph::Graph(int V)

{

    this->V = V;

    adj = new list<AdjListNode>[V];

}

 

void Graph::addEdge(int u, int v, int weight)

{

    AdjListNode node(v, weight);

    adj[u].push_back(node);

}

 

// 拓扑排序,递归调用。详细解释参考这里:

void Graph::topologicalSortRecall(int v, bool visited[], stack<int> &stk)

{

    // 标记当前节点是访问过的

    visited[v] = true;

 

    list<AdjListNode>::iterator i;

    for (i = adj[v].begin(); i != adj[v].end(); ++i)

    {

        AdjListNode node = *i;

        if (!visited[node.getV()])

            topologicalSortRecall(node.getV(), visited, stk);

    }

    stk.push(v);

}

 

// 从给定的源点s 找出到其它顶点的最短距离.

void Graph::shortestPath(int s)

{

    stack<int> stk;

    int dist[V];

 

    //标记所有顶点为未访问过的

    bool *visited = new bool[V];

    for (int i = 0; i < V; i++)

        visited[i] = false;

 

    // 拓扑排序,结果存入stk中

    for (int i = 0; i < V; i++)

        if (visited[i] == false)

            topologicalSortRecall(i, visited, stk);

 

    // 初始化距离

    for (int i = 0; i < V; i++)

        dist[i] = INF;

    dist[s] = 0;

 

    // 按照拓扑排序的顺序处理 各个顶点

    while (stk.empty() == false)

    {

        // 获得拓扑排序的下一个顶点

        int u = stk.top();

        stk.pop();

 

        // 更新所有相邻的顶点

        list<AdjListNode>::iterator i;

        if (dist[u] != INF)

        {

          for (i = adj[u].begin(); i != adj[u].end(); ++i)

             if (dist[i->getV()] > dist[u] + i->getWeight())

                dist[i->getV()] = dist[u] + i->getWeight();

        }

    }

 

    // 打印结果

    for (int i = 0; i < V; i++)

        (dist[i] == INF)? cout << "INF ": cout << dist[i] << " ";

}

 

// 测试

int main()

{

 

    Graph g(6);

    g.addEdge(0, 1, 5);

    g.addEdge(0, 2, 3);

    g.addEdge(1, 3, 6);

    g.addEdge(1, 2, 2);

    g.addEdge(2, 4, 4);

    g.addEdge(2, 5, 2);

    g.addEdge(2, 3, 7);

    g.addEdge(3, 4, -1);

    g.addEdge(4, 5, -2);

    int s = 1;

    cout << "Following are shortest distances from source " << s <<" \n";

    g.shortestPath(s);

 

    return 0;

}

 

 

 

时间复杂度

拓扑排序的时间复杂度是O(V + E)。找到拓扑顺序后,算法依次处理所有顶和其相邻顶点的顶点。总相邻顶点的个数是O(E)。因此,内循环运行O(V + E)。所以,这个算法的总体时间复杂度是O(V + E)。

 

转载地址:http://cwuii.baihongyu.com/

你可能感兴趣的文章
Linux下安装Python环境并部署NLP项目
查看>>
Nginx篇-springCloud配置Gateway+Nginx进行反向代理和负载均衡
查看>>
Nginx篇-Nginx配置动静分离
查看>>
缓存篇-Redis缓存失效以及解决方案
查看>>
缓存篇-使用Redis进行分布式锁应用
查看>>
缓存篇-Redisson的使用
查看>>
phpquery抓取网站内容简单介绍
查看>>
找工作准备的方向(4月22日写的)
查看>>
关于fwrite写入文件后打开查看是乱码的问题
查看>>
用结构体指针前必须要用malloc,不然会出现段错误
查看>>
Linux系统中的美
查看>>
一些实战项目(linux应用层编程,多线程编程,网络编程)
查看>>
我觉得专注于去学东西就好了,与世无争。
查看>>
原来k8s docker是用go语言写的,和现在所讲的go是一个东西!
查看>>
STM32CubeMX 真的不要太好用
查看>>
STM32CubeMX介绍、下载与安装
查看>>
不要买铝合金机架的无人机,不耐摔,易变形弯曲。
查看>>
ACfly也是基于FreeRTOS的
查看>>
F330装GPS的位置
查看>>
pixhawk也可以用Airsim仿真
查看>>