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

时间:2016-07-19 02:12:31

标签: c++ algorithm sorting graph-theory graph-algorithm

是否有一种在不可变图上进行高效,非递归拓扑排序的好方法?我的情况是我遍历通过指针链接在一起的图形,我需要进行拓扑排序。不修改图表对我来说很重要,但我不确定如何将节点标记为已访问并在不这样做的情况下有效地检查它。目前,我有一套存储标记,但我知道搜索发生在<div class='DOM'> // parent div.DOM ---> A <div class='DOM_A'> <p class='DOM_A_1'>some text</p> </div> <div class='DOM_B'> // children(1) ---> B <div class='DOM_B_1'> // children(0) ---> C <h1 class='DOM_B_1_1'>some heading</h1> <p class='DOM_B_1_2'>some text</p> // children(1) ---> D </div> </div> </div> // A // B // C // D ('div.DOM', 0)->children(1)->children(0)->children(1) 时间。有没有办法更好地做到这一点?这是一些有效的代码:

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

以上内容应该首先进行深度搜索。并且,是的,我意识到这是一个有趣的图表树,但左右元素不必指向独特的元素。无论如何,请提前感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

  

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。访问 nth 节点(其id为n)时,设置 nth bitmap上的位。如果要查看是否已访问给定节点,只需检查是否已设置相应的位。

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

答案 1 :(得分:0)

对数复杂度几乎总是足够快。 std :: map和std :: set还具有优于哈希表的另一个优点 - 性能在100%的时间内得到保证,这个属性在例如非常有用。硬实时系统。哈希表大多数时候比红黑树(地图,集合)快,但是例如如果需要进行重组,最糟糕的表现会让你感到震惊。