简体   繁体   English

如何在不可变图上进行有效的,非递归的拓扑排序

[英]How to do an efficient, nonrecursive topological sort on an immutable graph

Is there a good way to do an efficient, nonrecursive topological sort on an immutable graph? 是否有一种在不变图上进行有效,非递归拓扑排序的好方法? I've a situation where I'm traversing a graph linked together by pointers and I need to do a topological sort. 我遇到的情况是遍历由指针链接在一起的图形,并且需要进行拓扑排序。 It's important to me not to modify the graph, but I'm not sure how to mark a node as visited and check it efficiently without doing so. 对我来说,重要的是不要修改图形,但是我不确定如何将节点标记为已访问并在不这样做的情况下有效地对其进行检查。 At the moment, I have a set to store the markings, but I know the search occurs in log(m) time. 目前,我有一套存储标记的工具,但是我知道搜索发生在log(m)时间。 Is there a way to do this better? 有办法更好地做到这一点吗? Here's some working code: 这是一些工作代码:

// For std::shared_ptr
#include <memory>

// For std::list, std::stack, std::set
#include <list>
#include <stack>
#include <set>

// For std::cout
#include <iostream>

// Node in a directed acyclic graph with only two exiting edges
struct Node {
    // Identity of the node for debugging
    char identity;

    // Left and right branches
    std::shared_ptr <Node> left;
    std::shared_ptr <Node> right;

    // Create a node
    Node(
        char const & identity_,
        std::shared_ptr <Node> const & left_,
        std::shared_ptr <Node> const & right_
    ) : identity(identity_), left(left_), right(right_)
    {}
};


// Determines a topological sort of a directed acyclic graph of compute nodes
std::list <std::shared_ptr <Node>> topo_sort(
    std::shared_ptr <Node> const & root
) {
    // Add the root node to the todo list.  The todo list consists of
    // (ptr,whether we've searched left,whether we've searched right).
    auto todo = std::stack <std::tuple <std::shared_ptr <Node>,bool,bool>> ();
    todo.push(std::make_tuple(root,false,false));

    // Add an empty list for the sorted elements
    auto sorted = std::list <std::shared_ptr <Node>> {};

    // Keep track of which nodes have been marked
    auto marked = std::set <std::shared_ptr <Node>> {root};

    // Determines if a node has been marked
    auto is_marked = [&](auto const & node) {
        return marked.find(node)!=marked.end();
    };

    // Loop over the elements in the todo stack until we have none left to
    // process
    while(todo.size()>0) {
        // Grab the current node
        auto & current = todo.top(); 
        auto & node = std::get <0> (current);
        auto & searched_left = std::get <1> (current);
        auto & searched_right = std::get <2> (current);

        // Grab the left and right nodes
        auto left = node->left;
        auto right = node->right;

        // Do a quick check to determine whether we actually have children
        if(!left)
            searched_left = true;
        if(!right)
            searched_right = true;

        // If we've already transversed both left and right, add the node to
        // the sorted list
        if(searched_left && searched_right) {
            sorted.push_front(node);
            marked.insert(node);
            todo.pop();

        // Otherwise, traverse the left branch if that node hasn't been marked
        } else if(!searched_left) {
            searched_left = true;
            if(!is_marked(left)) {
                todo.push(std::make_tuple(left,false,false));
                marked.insert(left);
            }

        // Next, traverse the right branch if that node hasn't been marked
        } else if(!searched_right) {
            searched_right = true;
            if(!is_marked(right)) {
                todo.push(std::make_tuple(right,false,false));
                marked.insert(right);
            }
        }
    }

    // Return the topological sort
    return sorted;
}

int main() {
    // Create a graph with some dependencies
    auto a = std::make_shared <Node> ('a',nullptr,nullptr);
    auto b = std::make_shared <Node> ('b',nullptr,nullptr);
    auto c = std::make_shared <Node> ('c',a,a);
    auto d = std::make_shared <Node> ('d',b,c);
    auto e = std::make_shared <Node> ('e',d,c);
    auto f = std::make_shared <Node> ('f',e,c);

    // Sort the elements
    auto sorted = topo_sort(f);

    // Print out the sorted order
    for(auto const & node : sorted)
        std::cout << node->identity << std::endl;
}

which gives 这使

f
e
d
c
a
b

The above should do a depth first search for the sort. 上面应该做一个深度优先的搜索。 And, yes, I realize this is a funny looking tree for a graph, but the left and right elements don't have to point at unique elements. 而且,是的,我意识到这是一幅有趣的树形图,但是左右元素不必指向唯一元素。 In any case, thanks in advance for the help. 无论如何,请先感谢您的帮助。

std::unorderd_set Solution std :: unorderd_set解决方案

Instead of std::set<std::shared_ptr<Node>> , you can use std::unordered_set<Node*> to mark the visited nodes. 代替std::set<std::shared_ptr<Node>> ,您可以使用std::unordered_set<Node*>标记访问的节点。 unordered_set use hash to index nodes (complexity: constant on average), and it should be faster than set in most cases. unordered_set使用散列来索引节点(复杂性:平均为常数),并且在大多数情况下,它应比set速度更快。 Also save the raw pointer, ie Node* , in container, is faster than shared_ptr , since there's no reference count operations. 另外,将原始指针(即Node*保存在容器中的速度比shared_ptr快,因为没有引用计数操作。

If this solution takes too much memory, you can try the bitmap solution. 如果此解决方案占用太多内存,则可以尝试使用bitmap解决方案。

bitmap Solution 位图解决方案

Give each node an id beginning from 0, and use a bitmap to save the visited status. 给每个节点一个从0开始的id,并使用bitmap保存访问状态。

Initialize the bitmap with all bits setting to 0. When visiting the nth node (whose id is n ), set the nth bit on the bitmap . 将所有位都设置为0初始化bitmap 。访问第n个节点(其ID为n )时,将bitmap上的第n位设置为。 When you want to find out if a given node has been visited, you just check whether the corresponding bit has been set. 当您要查找是否已访问给定节点时,只需检查是否已设置相应的位。

This solution only takes n bits of memory, where n is the number of nodes in your tree. 此解决方案仅占用n位内存,其中n是树中节点的数量。

Logarithmic complexity is almost always fast enough. 对数复杂度几乎总是足够快。 std::map and std::set also have another advantage over hash tables - the performance is guaranteed 100% of the time, a property very useful in eg hard real-time systems. 与哈希表相比,std :: map和std :: set还具有另一个优势-保证100%的时间性能,该属性在例如硬实时系统中非常有用。 A hash table is faster than a red-black tree (map,set) most of the time, but eg if a rehash is necessary, worst-case performance will hit you. 哈希表通常在大多数情况下比红黑树(映射,设置)快,但是例如,如果需要重新哈希,则最坏情况下的性能会受到打击。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM