繁体   English   中英

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

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

是否有一种在不变图上进行有效,非递归拓扑排序的好方法? 我遇到的情况是遍历由指针链接在一起的图形,并且需要进行拓扑排序。 对我来说,重要的是不要修改图形,但是我不确定如何将节点标记为已访问并在不这样做的情况下有效地对其进行检查。 目前,我有一套存储标记的工具,但是我知道搜索发生在log(m)时间。 有办法更好地做到这一点吗? 这是一些工作代码:

// 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;
}

这使

f
e
d
c
a
b

上面应该做一个深度优先的搜索。 而且,是的,我意识到这是一幅有趣的树形图,但是左右元素不必指向唯一元素。 无论如何,请先感谢您的帮助。

std :: unorderd_set解决方案

代替std::set<std::shared_ptr<Node>> ,您可以使用std::unordered_set<Node*>标记访问的节点。 unordered_set使用散列来索引节点(复杂性:平均为常数),并且在大多数情况下,它应比set速度更快。 另外,将原始指针(即Node*保存在容器中的速度比shared_ptr快,因为没有引用计数操作。

如果此解决方案占用太多内存,则可以尝试使用bitmap解决方案。

位图解决方案

给每个节点一个从0开始的id,并使用bitmap保存访问状态。

将所有位都设置为0初始化bitmap 。访问第n个节点(其ID为n )时,将bitmap上的第n位设置为。 当您要查找是否已访问给定节点时,只需检查是否已设置相应的位。

此解决方案仅占用n位内存,其中n是树中节点的数量。

对数复杂度几乎总是足够快。 与哈希表相比,std :: map和std :: set还具有另一个优势-保证100%的时间性能,该属性在例如硬实时系统中非常有用。 哈希表通常在大多数情况下比红黑树(映射,设置)快,但是例如,如果需要重新哈希,则最坏情况下的性能会受到打击。

暂无
暂无

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

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