[英]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.