帮助我理解中序遍历而不使用递归
Posted
技术标签:
【中文标题】帮助我理解中序遍历而不使用递归【英文标题】:Help me understand Inorder Traversal without using recursion 【发布时间】:2011-01-08 04:03:04 【问题描述】:我能够在不使用递归的情况下理解前序遍历,但我在中序遍历方面遇到了困难。我只是似乎不明白,也许是因为我不了解递归的内部工作原理。
这是我迄今为止尝试过的:
def traverseInorder(node):
lifo = Lifo()
lifo.push(node)
while True:
if node is None:
break
if node.left is not None:
lifo.push(node.left)
node = node.left
continue
prev = node
while True:
if node is None:
break
print node.value
prev = node
node = lifo.pop()
node = prev
if node.right is not None:
lifo.push(node.right)
node = node.right
else:
break
内部的while循环感觉不对。此外,一些元素被打印两次;可能我可以通过检查该节点之前是否已打印来解决此问题,但这需要另一个变量,这再次感觉不对。我哪里错了?
我没有尝试过后序遍历,但我想它是相似的,我也会在那里面临同样的概念障碍。
感谢您的宝贵时间!
P.S.:Lifo
和 Node
的定义:
class Node:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
class Lifo:
def __init__(self):
self.lifo = ()
def push(self, data):
self.lifo = (data, self.lifo)
def pop(self):
if len(self.lifo) == 0:
return None
ret, self.lifo = self.lifo
return ret
【问题讨论】:
【参考方案1】:为了编写这些递归方法的迭代等价物,我们可以首先了解递归方法本身是如何在程序的堆栈上执行的。假设节点没有父指针,我们需要为迭代变体管理自己的“堆栈”。
开始的一种方法是查看递归方法并标记调用将“恢复”的位置(新的初始调用,或递归调用返回后)。下面这些标记为“RP 0”、“RP 1”等(“恢复点”)。以中序遍历为例。 (我将使用 C 语言进行演示,但同样的方法适用于任何通用语言):
void in(node *x)
/* RP 0 */
if(x->lc) in(x->lc);
/* RP 1 */
process(x);
if(x->rc) in(x->rc);
/* RP 2 */
它的迭代变体:
void in_i(node *root)
node *stack[1000];
int top;
char pushed;
stack[0] = root;
top = 0;
pushed = 1;
while(top >= 0)
node *curr = stack[top];
if(pushed)
/* type (x: 0) */
if(curr->lc)
stack[++top] = curr->lc;
continue;
/* type (x: 1) */
pushed = 0;
process(curr);
top--;
if(curr->rc)
stack[++top] = curr->rc;
pushed = 1;
带有(x: 0)
和(x: 1)
的代码cmets对应递归方法中的“RP 0”和“RP 1”恢复点。 pushed
标志帮助我们推断出这两个恢复点之一。我们不需要在“RP 2”阶段处理节点,因此我们不会将此类节点保留在堆栈中。
【讨论】:
【参考方案2】:这是一个用于中序遍历的迭代 Python 代码 ::
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
def inOrder(root):
current = root
s = []
done = 0
while(not done):
if current is not None :
s.append(current)
current = current.left
else :
if (len(s)>0):
current = s.pop()
print current.data
current = current.right
else :
done =1
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
inOrder(root)
【讨论】:
【参考方案3】:这是一个迭代 C++ 解决方案,可以替代 @Emadpres 发布的内容:
void inOrderTraversal(Node *n)
stack<Node *> s;
s.push(n);
while (!s.empty())
if (n)
n = n->left;
else
n = s.top(); s.pop();
cout << n->data << " ";
n = n->right;
if (n) s.push(n);
【讨论】:
【参考方案4】:class Tree:
def __init__(self, value):
self.left = None
self.right = None
self.value = value
def insert(self,root,node):
if root is None:
root = node
else:
if root.value < node.value:
if root.right is None:
root.right = node
else:
self.insert(root.right, node)
else:
if root.left is None:
root.left = node
else:
self.insert(root.left, node)
def inorder(self,tree):
if tree.left != None:
self.inorder(tree.left)
print "value:",tree.value
if tree.right !=None:
self.inorder(tree.right)
def inorderwithoutRecursion(self,tree):
holdRoot=tree
temp=holdRoot
stack=[]
while temp!=None:
if temp.left!=None:
stack.append(temp)
temp=temp.left
print "node:left",temp.value
else:
if len(stack)>0:
temp=stack.pop();
temp=temp.right
print "node:right",temp.value
【讨论】:
欢迎来到 SO。请记住在代码中添加 4 空格缩进,以便正确显示。另外,我建议为其添加一些注释。 OP 也要求一些解释,所以这里有点需要注释。【参考方案5】:没有递归的简单迭代中序遍历
'''iterative inorder traversal, O(n) time & O(n) space '''
class Node:
def __init__(self, value, left = None, right = None):
self.value = value
self.left = left
self.right = right
def inorder_iter(root):
stack = [root]
current = root
while len(stack) > 0:
if current:
while current.left:
stack.append(current.left)
current = current.left
popped_node = stack.pop()
current = None
if popped_node:
print popped_node.value
current = popped_node.right
stack.append(current)
a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
b.right = d
a.left = b
a.right = c
inorder_iter(a)
【讨论】:
【参考方案6】:这可能会有所帮助(Java 实现)
public void inorderDisplay(Node root)
Node current = root;
LinkedList<Node> stack = new LinkedList<>();
while (true)
if (current != null)
stack.push(current);
current = current.left;
else if (!stack.isEmpty())
current = stack.poll();
System.out.print(current.data + " ");
current = current.right;
else
break;
【讨论】:
【参考方案7】:@Emadpres 对答案的小优化
def in_order_search(node):
stack = Stack()
current = node
while True:
while current is not None:
stack.push(current)
current = current.l_child
if stack.size() == 0:
break
current = stack.pop()
print(current.data)
current = current.r_child
【讨论】:
【参考方案8】:这是一个在 c# (.net) 中使用堆栈的顺序遍历示例:
(后期订单迭代可以参考:Post order traversal of binary tree without recursion)
public string InOrderIterative()
List<int> nodes = new List<int>();
if (null != this._root)
Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();
var iterativeNode = this._root;
while(iterativeNode != null)
stack.Push(iterativeNode);
iterativeNode = iterativeNode.Left;
while(stack.Count > 0)
iterativeNode = stack.Pop();
nodes.Add(iterativeNode.Element);
if(iterativeNode.Right != null)
stack.Push(iterativeNode.Right);
iterativeNode = iterativeNode.Right.Left;
while(iterativeNode != null)
stack.Push(iterativeNode);
iterativeNode = iterativeNode.Left;
return this.ListToString(nodes);
这是一个带有visited标志的示例:
public string InorderIterative_VisitedFlag()
List<int> nodes = new List<int>();
if (null != this._root)
Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();
BinaryTreeNode iterativeNode = null;
stack.Push(this._root);
while(stack.Count > 0)
iterativeNode = stack.Pop();
if(iterativeNode.visted)
iterativeNode.visted = false;
nodes.Add(iterativeNode.Element);
else
iterativeNode.visted = true;
if(iterativeNode.Right != null)
stack.Push(iterativeNode.Right);
stack.Push(iterativeNode);
if (iterativeNode.Left != null)
stack.Push(iterativeNode.Left);
return this.ListToString(nodes);
binarytreenode、listtostring 实用程序的定义:
string ListToString(List<int> list)
string s = string.Join(", ", list);
return s;
class BinaryTreeNode
public int Element;
public BinaryTreeNode Left;
public BinaryTreeNode Right;
【讨论】:
【参考方案9】:从递归算法开始(伪代码):
traverse(node):
if node != None do:
traverse(node.left)
print node.value
traverse(node.right)
endif
这是一个明显的尾递归案例,因此您可以轻松地将其转换为 while 循环。
traverse(node):
while node != None do:
traverse(node.left)
print node.value
node = node.right
endwhile
剩下一个递归调用。递归调用所做的是在堆栈上推送一个新的上下文,从头开始运行代码,然后检索上下文并继续做它正在做的事情。因此,您创建了一个用于存储的堆栈和一个循环,该循环在每次迭代时确定我们是处于“第一次运行”情况(非空节点)还是“返回”情况(空节点,非空堆栈) 并运行相应的代码:
traverse(node):
stack = []
while !empty(stack) || node != None do:
if node != None do: // this is a normal call, recurse
push(stack,node)
node = node.left
else // we are now returning: pop and print the current node
node = pop(stack)
print node.value
node = node.right
endif
endwhile
难以掌握的是“返回”部分:您必须在循环中确定您正在运行的代码是处于“进入函数”的情况还是“从调用中返回”的情况,并且您将拥有一个 if/else
链,其中包含与代码中的非终端递归一样多的情况。
在这种特定情况下,我们使用节点来保存有关情况的信息。另一种方法是将其存储在堆栈本身中(就像计算机进行递归一样)。使用这种技术,代码不是最优的,但更容易理解
traverse(node):
// entry:
if node == NULL do return
traverse(node.left)
// after-left-traversal:
print node.value
traverse(node.right)
traverse(node):
stack = [node,'entry']
while !empty(stack) do:
[node,state] = pop(stack)
switch state:
case 'entry':
if node == None do: break; // return
push(stack,[node,'after-left-traversal']) // store return address
push(stack,[node.left,'entry']) // recursive call
break;
case 'after-left-traversal':
print node.value;
// tail call : no return address
push(stack,[node.right,'entry']) // recursive call
end
endwhile
【讨论】:
@Victor:谢谢!您提示考虑必须在“进入函数”情况和“从调用返回”情况下运行的代码部分帮助我直观地理解。另外,感谢您展开尾递归的中间步骤;我听说过它,但看到它的实际效果很有帮助! 这是一个很好的解释...我以一种困难的方式想出了同样的方法..但是上述逐步分解的方式使它理解起来非常简单 我不认为traverse(node): if node != None do: traverse(node.left) print node.value traverse(node.right) endif
是尾递归的
我同意@JacksonTale。这绝对不是一个尾递归的明确案例。尾递归需要一个递归调用。递归树遍历其实是一个typical example的非尾递归。
嗨@Victor,这是关于这个主题的最好的文章。你能详细说明 pre_order_traversal 和 post_order_traversal 吗? ^_^【参考方案10】:
这是一个简单的有序非递归 c++ 代码..
void inorder (node *n)
stack s;
while(n)
s.push(n);
n=n->left;
while(!s.empty())
node *t=s.pop();
cout<<t->data;
t=t->right;
while(t)
s.push(t);
t = t->left;
【讨论】:
【参考方案11】: def print_tree_in(根): 堆栈 = [] 当前 = 根 而真: 虽然当前不是无: stack.append(当前) 当前 = current.getLeft(); 如果不堆叠: 返回 当前 = stack.pop() 打印 current.getValue() 而 current.getRight 是 None 和堆栈: 当前 = stack.pop() 打印 current.getValue() 当前 = current.getRight();【讨论】:
【参考方案12】:@Victor,我对您尝试将状态推入堆栈的实现有一些建议。我不认为这是必要的。因为您从堆栈中取出的每个元素都已被遍历。因此,我们不需要将信息存储到堆栈中,而是需要一个标志来指示要处理的下一个节点是否来自该堆栈。以下是我的实现,效果很好:
def intraverse(node):
stack = []
leftChecked = False
while node != None:
if not leftChecked and node.left != None:
stack.append(node)
node = node.left
else:
print node.data
if node.right != None:
node = node.right
leftChecked = False
elif len(stack)>0:
node = stack.pop()
leftChecked = True
else:
node = None
【讨论】:
【参考方案13】:状态可以被隐式记住,
traverse(node)
if(!node) return;
push(stack, node);
while (!empty(stack))
/*Remember the left nodes in stack*/
while (node->left)
push(stack, node->left);
node = node->left;
/*Process the node*/
printf("%d", node->data);
/*Do the tail recursion*/
if(node->right)
node = node->right
else
node = pop(stack); /*New Node will be from previous*/
【讨论】:
否定。这个版本卡在树底部的无限循环中。【参考方案14】:我认为部分问题在于“prev”变量的使用。您不必存储上一个节点,您应该能够在堆栈 (Lifo) 本身上维护状态。
来自Wikipedia,你的目标算法是:
-
访问根目录。
遍历左子树
遍历右子树
在伪代码中(免责声明,我不了解 Python,因此为下面的 Python/C++ 样式代码道歉!)您的算法将类似于:
lifo = Lifo();
lifo.push(rootNode);
while(!lifo.empty())
node = lifo.pop();
if(node is not None)
print node.value;
if(node.right is not None)
lifo.push(node.right);
if(node.left is not None)
lifo.push(node.left);
对于后序遍历,您只需交换将左右子树推入堆栈的顺序。
【讨论】:
@Paolo:这是前序遍历,而不是有序遍历。无论如何,谢谢你的回复:)【参考方案15】:def traverseInorder(node):
lifo = Lifo()
while node is not None:
if node.left is not None:
lifo.push(node)
node = node.left
continue
print node.value
if node.right is not None:
node = node.right
continue
node = lifo.Pop()
if node is not None :
print node.value
node = node.right
PS:我不懂 Python,所以可能会有一些语法问题。
【讨论】:
以上是关于帮助我理解中序遍历而不使用递归的主要内容,如果未能解决你的问题,请参考以下文章