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