我正在尝试在C ++中创建一个trie结构,其中每个节点都包含一个名称和一些数据,并且可以包含对任意数量的子节点的引用。
我想遍历我的特里,以便以迭代方式打印出到给定节点的“扁平化”路径。
给出一些树,其中节点的定义如下:
class Node
{
public:
virtual std::string node_name() = 0;
};
class TreeNode : Node
{
public:
std::string name;
std::map<std::string, TreeNode&> children {};
TreeNode(const std::string&name) : name(name) {}
std::string node_name()
{
return name;
}
void add_child(TreeNode& node)
{
children.insert(std::pair<std::string, TreeNode&>
(node.node_name(), node));
}
};
如果我通过以下方式添加孩子:
TreeNode root { "root" };
TreeNode a { "a" };
TreeNode b { "b" };
TreeNode c { "c" };
TreeNode d { "d" };
TreeNode e { "e" };
a.add_child(c);
a.add_child(d);
b.add_child(e);
root.add_child(a);
root.add_child(b);
// flatten the tree and print out the namespace here
flatten(root);
输出应为(不关心顺序):
root
root.a
root.a.c
root.a.d
root.b
root.b.e
我已经实现了递归解决方案(请参阅下文),但是我想知道是否有一种迭代的方式(因为我想尽可能避免在应用程序中递归)。
这是递归版本:
void flatten_helper(TreeNode& root,
const std::string& prefix)
{
static const std::string delimeter { "." };
std::string namespace_path(prefix);
if (!prefix.empty())
{
namespace_path.append(delimeter);
}
namespace_path.append(root.node_name());
for (auto& node : root.children)
{
flatten_helper(node.second, namespace_path);
}
// do something with node/complete namespace name
std::cout << namespace_path << std::endl;
}
void flatten(TreeNode& node)
{
std::string empty {""};
return flatten_helper(node, empty);
}
int main(int argc, char** argv)
{
TreeNode root { "root" };
TreeNode a { "a" };
TreeNode b { "b" };
TreeNode c { "c" };
TreeNode d { "d" };
TreeNode e { "e" };
a.add_child(c);
a.add_child(d);
b.add_child(e);
root.add_child(a);
root.add_child(b);
flatten(root);
return 0;
}
我对如何使用c ++ 11中的迭代版本感到有些困惑(已经尝试了各种树遍历的几次迭代)-任何帮助将不胜感激。
答案 0 :(得分:1)
将递归过程转换为交互过程的技巧是使“待处理的工作”明确。在您的情况下,工作单位是TreeNode
和前缀,而工作单位则保存在std::stack
中(因为您的递归解决方案是深度优先的)。任何(以前)递归调用都必须将工作添加到堆栈中,并且在没有更多工作可用时,工作停止。
void flatten_iter(TreeNode& root_node)
{
using WorkItem = std::pair<TreeNode&, std::string>;
static const std::string delimeter{ "." };
std::stack<WorkItem> workitems;
workitems.emplace(root_node, "");
while (!workitems.empty()) {
auto [ node, prefix ] = workitems.top();
workitems.pop();
std::string namespace_path(prefix);
if (!prefix.empty())
{
namespace_path.append(delimeter);
}
namespace_path.append(node.node_name());
for (auto& child : node.children)
{
workitems.emplace(child.second, namespace_path);
}
// do something with node/complete namespace name
std::cout << namespace_path << std::endl;
}
}
答案 1 :(得分:0)
一种根本避免使用堆栈的方法是在树中使用父指针。
您的迭代器可以跟踪树,处理底部分支中的所有对等节点,然后使用最后一个节点中的父指针来恢复1级,继续前进到该级的下一个对等节点,然后再下来。最后,它是一个相对最小的算法。
当然,对于一棵大树,每个节点上的父指针的开销要比显式堆栈的开销大/多/,但是如果您还有其他用途要使用父指针,则值得考虑。