是否存在将高递归函数优化为数据结构迭代的算法?例如,给定以下功能......
// template <typename T> class BSTNode is defined somewhere else
// It's a generic binary search tree.
template <typename T, typename R>
void in_order(BSTNode<T>* root, R (*routine)(T))
{
if (root)
{
in_order(root->leftChild);
routine(root->data);
in_order(root->rightChild);
}
}
...是否有可能将其优化为......
// template <typename> class Stack is defined somewhere else
// It's a generic LIFO array (aka stack).
template <typename T, typename R>
void in_order(BSTNode<T>* root, R (*routine)(T))
{
if (!root) return;
Stack<BSTNode*> stack;
stack.push(NULL);
line1:
while (root->leftChild)
{
stack.push(root);
root = root->leftChild;
}
line2:
routine(root->data);
if (root->rightChild)
{
root = root->rightChild;
goto line1;
}
else if (root = stack.pop())
goto line2;
}
(当然,区别在于,不是填充调用堆栈,而是填充堆中的另一个堆栈。)
编辑:我的意思是一个可以由编译器执行的算法,因此我不必手动优化它。
答案 0 :(得分:4)
是的,你可以这样做。
然而,除了可能耗尽非常深的树木的堆叠空间外,这里没有“优化”。如果您需要提速,请考虑使用threading your trees。
答案 1 :(得分:4)
我遇到的唯一一般递归优化是优化尾递归。这通常在函数式语言中完成,并且基本上涉及编译器/解释器更改函数,其中最终操作是递归调用到完全迭代函数(因此堆栈等没有问题)。
对于其他形式的递归,我不知道已经找到/创建了用于将它们优化为迭代函数的任何通用算法。你当然可以总是以迭代的方式表达这些函数,但转换不是通用的。
答案 2 :(得分:2)
您要求continuation-passing-style转换defunctionalized转换;它包含在Essentials of Programming Languages的第6章中,代码在Scheme中。但是为C ++实现它将是一个巨大的痛苦。也许如果你有一个编译器前端可以将C ++转换为合理可访问的数据结构,那么你需要对 lot 代码执行此操作。本书还解释了如何系统地手动进行这种转换,这在您的情况下更有可能实用。
答案 3 :(得分:1)
从技术上讲,答案是“是”:任何可以递归表达的算法(即带有隐式堆栈)也可以重新构造为使用迭代(即使用显式堆栈或其他跟踪结构)。
但是,我怀疑大多数编译器不能或不会尝试为您执行此操作。使用通用自动算法进行转换可能相当困难,虽然我从来没有尝试过,所以我不应该说这是难以处理的。
答案 4 :(得分:1)
可以在不使用递归的情况下首先遍历树深度。
以下是一个很好的例子:http://code.activestate.com/recipes/461776/。
但编译器不会为您执行此优化。但是,这个概念并不那么难以理解。你正在做的是自己创建一个调用堆栈和节点列表,而不是使用函数调用深入到树中。
答案 5 :(得分:1)
可以迭代地遍历一个可变的有序树,通过记录你所在分支中的节点的父节点,并知道你从一个节点接近一个节点的方向(从左边或右边向上或向上,这是有序的属性树允许你测试):
template <typename T, typename R>
void in_order ( BSTNode<T>* root, R (*routine)(T) ) {
typedef BSTNode<T>* Node;
Node current = root;
Node parent = 0;
bool going_down = true;
while ( current ) {
Node next = 0;
if ( going_down ) {
if ( current -> leftChild ) {
// navigate down the left, swapping prev with the path taken
Node next_child = current -> leftChild;
current -> leftChild = parent;
parent = current;
current = next_child;
} else if ( current -> rightChild ) {
// navigate down the right, swapping prev with the path taken
Node next_child = current -> rightChild;
current -> rightChild = parent;
parent = current;
current = next_child;
} else {
// leaf
routine ( current -> data );
going_down = false;
}
} else {
// moving up to parent
if ( parent ) {
Node next_parent = 0;
// came from the left branch
if ( parent -> data > current -> data ) {
// visit parent after left branch
routine ( parent -> data );
// repair parent
next_parent = parent -> leftChild;
parent -> leftChild = current;
// traverse right if possible
if ( parent -> rightChild ) {
going_down = true;
// navigate down the right, swapping prev with the path taken
Node next_child = parent -> rightChild;
parent -> rightChild = next_parent;
//parent = current;
current = next_child;
continue;
}
} else {
// came from the right branch
next_parent = parent -> rightChild;
parent -> rightChild = current;
}
current = parent;
parent = next_parent;
} else {
break;
}
}
}
}
如果不是存储子项,而是存储父项和子项的XOR,那么您可以从任何方向获取下一个节点,而不必改变树。
我不知道有什么能自动将非尾递归函数转换为这样的代码。我知道在堆上分配调用堆栈的环境,这可以在不能执行此类突变并具有固定的小堆栈大小的情况下透明地避免堆栈溢出。通常在堆栈上记录状态占用调用堆栈的空间较少,因为您只选择要记录的基本本地状态,并且不记录返回地址或任何调用者保存寄存器(如果您使用函子而不是函数指针,那么编译器更有可能内联routine
,因此不能在简单的递归情况下保存调用者保存寄存器,因此减少每次递归所需的堆栈量.YMMV)。
由于您正在使用模板,因此您只需要执行一次遍历代码,并将其与策略模板结合使用,以便在pre,post和inorder之间切换,或者在您想要的任何其他迭代模式之间切换。
答案 6 :(得分:0)
你自己回答了。什么是更便宜,堆栈或堆? (除非你的堆栈空间不足)当然,区别在于, 而不是填充调用堆栈, 后者填补了另一个堆栈 堆。
答案 7 :(得分:0)
当然你可以制作自己的筹码。
你需要速度吗?除非routine(root->data)
几乎没有任何作用,否则你永远不会注意到差异。