草庐IT

有向图的拓扑排序——DFS

YVVT’s Blog 2023-03-28 原文

有向图的拓扑排序——BFS这篇文章中,介绍了有向图的拓扑排序的定义以及使用广度优先搜索(BFS)对有向图进行拓扑排序的方法,这里再介绍另一种方法:深度优先搜索(DFS)。

算法

考虑下面这张图:

首先,我们需要维护一个栈,用来存放DFS到的节点。另外规定每个节点有两个状态:已访问(这里用蓝绿色表示)、未访问(这里用黑色表示)。

任选一个节点开始DFS,比如这里就从0开始吧。

首先将节点0的状态设为已访问,然后节点0的邻居(节点0的出边指向的节点)共有1个:节点2,它是未访问状态,于是顺下去访问节点2。

节点2的状态也设为已访问。节点2有3个邻居:3、4、5,都是未访问状态,不妨从3开始。一直这样访问下去,直到访问到没有出边的节点7。

节点7没有出边了,这时候就将节点7入栈。

退回到节点6,虽然6还有邻居,但是唯一的邻居节点7是已访问状态,也入栈。再次退回,节点4的两个邻居也都已访问,依旧入栈并后退。以此类推,退回到节点2。

节点2有3个邻居,其中节点3和4已访问,但是节点5还未访问,访问节点5。

接下来的步骤是一样的,不再赘述了,直接退回到节点0并将0入栈。

现在,从节点0开始的DFS宣告结束,但是图中还有未访问的节点:节点1,从节点1继续开始DFS。

节点1的邻居节点2已经访问过了,直接将节点1入栈。

至此,整个DFS过程宣告结束。从栈顶到栈底的节点序列1 0 2 5 3 4 6 7就是整个图的一个拓扑排序序列。

实现

这里同样使用类型别名node_t代表节点序号unsigned long long

using node_t = unsigned long long;

同样使用邻接表来存储图结构,整个图的定义如下:

class Graph {
    unsigned long long n;
    vector<vector<node_t>> adj;

protected:
    void dfs(node_t cur, vector<bool> &visited, stack<node_t> &nodeStack);

public:
    Graph(initializer_list<initializer_list<node_t>> list) : n(list.size()), adj({}) {
        for (auto &l : list) {
            adj.emplace_back(l);
        }
    }

    vector<node_t> toposortDfs();
};

DFS

函数dfs的参数及说明如下:

  • cur:当前访问的节点。
  • visited:存放各个节点状态的数组,false表示未访问,true表示已访问。初始化为全为false
  • nodeStack:存放节点的栈。

整个过程如下:

  1. 首先,我们需要将当前节点的状态设为已访问:
visited[cur] = true;
  1. 依次检查当前节点的所有邻居的状态:
for (node_t neighbor: adj[cur]) {
    // ...
}
  1. 如果某个节点已访问,则跳过。
if (visited[neighbor]) continue;
  1. 否则,递归的对该节点进行DFS:
dfs(neighbor, visited, nodeStack);
  1. 所有邻居检查完后,就将该节点入栈:
nodeStack.push(cur);

整个dfs函数的代码如下:

void Graph::dfs(node_t cur, vector<bool> &visited, stack<node_t> &nodeStack) {
    visited[cur] = true;
    for (node_t neighbor: adj[cur]) {
        if (visited[neighbor]) continue;
        dfs(neighbor, visited, nodeStack);
    }
    nodeStack.push(cur);
}

拓扑排序

首先,我们需要初始化3个数据结构:

  • sort:存放拓扑排序序列的数组。
  • visited:见上文。
  • nodeStack:见上文。
vector<node_t> sort;
vector<bool> visited(n, false);
stack<node_t> nodeStack;

整个过程如下:

  1. 依次检查每个节点的状态,如果未访问,则从该节点开始进行DFS:
for (node_t node = 0; node < n; ++node) {
    if (visited[node]) continue;
    dfs(node, visited, nodeStack);
}
  1. 此时nodeStack已经存储了整个拓扑排序序列,我们只需要转移到sort数组并返回即可:
while (!nodeStack.empty()) {
    sort.push_back(nodeStack.top());
    nodeStack.pop();
}
return sort;

整个代码如下:

vector<node_t> Graph::toposortDfs() {
    vector<node_t> sort;
    vector<bool> visited(n, false);
    stack<node_t> nodeStack;
    for (node_t node = 0; node < n; ++node) {
        if (visited[node]) continue;
        dfs(node, visited, nodeStack);
    }
    while (!nodeStack.empty()) {
        sort.push_back(nodeStack.top());
        nodeStack.pop();
    }
    return sort;
}

测试

代码:

int main() {
    Graph graph{{2},
                {2},
                {3, 4, 5},
                {4},
                {6, 7},
                {4},
                {7},
                {}};
    auto sort = graph.toposortDfs();
    cout << "The topology sort sequence is:\n";
    for (const auto &node: sort) {
        cout << node << ' ';
    }
    return 0;
}

输出:

The topology sort sequence is:
1 0 2 5 3 4 6 7

复杂度分析

  • 时间复杂度:\(O(n+e)\)\(n\)为节点总数,\(e\)为边的总数。其中DFS的时间复杂度为\(O(n+e)\)
  • 空间复杂度:\(O(n)\)(邻接表的空间复杂度为\(O(n+e)\),不计入在内),其中维护visited数组和nodeStack栈分别需要\(O(n)\)的额外空间。

有关有向图的拓扑排序——DFS的更多相关文章

  1. ruby-on-rails - 如何在 Ruby on Rails 中实现无向图? - 2

    我需要在RubyonRails中实现无向图G=(V,E)并考虑构建一个Vertex和一个Edge模型,其中Vertex有_多条边。由于边恰好连接两个顶点,您将如何在Rails中执行此操作?您是否知道任何有助于实现此类图表的gem或库(对重新发明轮子不感兴趣;-))? 最佳答案 不知道有任何现有库在ActiveRecord之上提供图形逻辑。您可能必须实现自己的Vertex、EdgeActiveRecord支持的模型(请参阅Rails安装的rails/activerecord中的vertex.rb和edge.rb/test/fixtur

  2. ruby-on-rails - 需要帮助最大化多个相似对象中的 3 个因素并适当排序 - 2

    我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night

  3. ruby-on-rails - 在具有 ActiveRecord 条件的相关模型中按字段排序 - 2

    我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我

  4. ruby - 按数字(从大到大)然后按字母(字母顺序)对对象集合进行排序 - 2

    我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby​​做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排

  5. ruby-on-rails - 在不重新查询数据库的情况下重新排序 Rails 中的事件记录? - 2

    例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果

  6. ruby-on-rails - 如何对对象数组进行排序? - 2

    我有一个对象如下:[{:id=>2,:fname=>"Ron",:lname=>"XXXXX",:photo=>"XXX"},{:id=>3,:fname=>"Dain",:lname=>"XXXX",:photo=>"XXXXXXX"},{:id=>1,:fname=>"Bob",:lname=>"XXXXXX",:photo=>"XXXX"}]我想按fname排序,不区分大小写,所以它会导致编号:1,3,2我该如何排序?我正在尝试:@people.sort!{|x,y|y[:fname]x[:fname]}但这没有任何效果。 最佳答案

  7. ruby - 使用自定义排序首选项对数组进行排序? - 2

    有人可以告诉我如何根据自定义字符串对嵌套数组进行排序吗?比如有没有办法排序:[['Red','Blue'],['Green','Orange'],['Purple','Yellow']]“橙色”、“黄色”,然后是“蓝色”?最终结果如下所示:[['Green','Orange'],['Purple','Yellow'],['Red','Blue']]它不是按字母顺序排序的。我很想知道我是否可以定义要排序的值以实现上述目标。 最佳答案 sort_by对于这种排序总是非常方便:a=[['Red','Blue'],['Green','Ora

  8. Ruby 将对象插入现有的已排序对象数组 - 2

    我有以下现有的Dog对象数组,它们按age属性排序:classDogattr_accessor:agedefinitialize(age)@age=ageendenddogs=[Dog.new(1),Dog.new(4),Dog.new(10)]我现在想插入一条新的狗记录,并将它放在数组中的正确位置。假设我想插入这个对象:another_dog=Dog.new(8)我想把它插入到数组中,让它成为数组中的第三项。这是一个人为的示例,旨在演示我特别想如何将一个项目插入到现有的有序数组中。我意识到我可以创建一个全新的数组并重新对所有对象进行排序,但这不是我的目标。谢谢!

  9. ruby - 如何排序不是简单的哈希(哈希的哈希) - 2

    我有一个这样的哈希{55=>{:value=>61,:rating=>-147},89=>{:value=>72,:rating=>-175},78=>{:value=>64,:rating=>-155},84=>{:value=>90,:rating=>-220},95=>{:value=>39,:rating=>-92},46=>{:value=>97,:rating=>-237},52=>{:value=>73,:rating=>-177},64=>{:value=>69,:rating=>-167},86=>{:value=>68,:rating=>-165},53=>{:va

  10. ruby - 根据值然后键对ruby中的哈希进行排序 - 2

    如何在ruby​​中先根据值然后根据键对散列进行排序?例如h={4=>5,2=>5,7=>1}将排序为[[7,1],[2,5],[4,5]]我可以根据值进行排序h.sort{|x,y|x[1]y[1]}但我不知道如何根据值进行排序,然后在值相同时键入 最佳答案 h.sort_by{|k,v|[v,k]}这使用了Array的事实混入Comparable并定义逐元素。注意上面等价于h.sort_by{|el|el.reverse}相当于h.sort_by(&:reverse)这可能会或可能不会更具可读性。如果你知道Hashes一般都是先

随机推荐