在前面的这篇文章中我写了二叉树遍历的递归实现,在这篇文章中我将讲述下二叉树的非递归实现。
大多数的递归问题的非递归算法,需要用栈来消除递归。栈是一种存储容器,同时又是一种控制结构,栈先进先出的控制结构,同时,调用时可用栈来保留必要的信息,退出时,可以从栈中取出信息,进行后续的处理。所以设计二叉树的遍历非递归算法, 需要用栈来保留节点的信息。
二叉树的结构
class Btree<T>{
T value;
Btree<T> left;
Btree<T> right;
BNode(T obj, BNode<T> left, BNode<T> right){
this.value = obj;
this.left = left;
this.right = right;
}
}
public void visitDate(Btree<T> root){
if (root != null) {
System.out.println(root.value);
}
}
1.先序遍历二叉树的非递归实现
遍历思路:
(1).访问根节点,根节点入栈并进入其左子树,进而访问左子树的根节点并入栈,在进入下一层左子树,……,如此重复,直至当前节点为空。
(2).如栈非空,则从栈顶退出上一层的节点,并进入该节点的右子树。
代码如下:
public void preOder(Btree<T> root){
Stack<Btree<T>> stack = new Stack<Btree<T>>();
Btree<T> p = root;//保留当前的节点
while(p != null || !stack.isEmpty()){
//遍历这个节点的子节点,直到到达这个节点的叶子节点
while(p != null){
visitDate(p);//输出当前节点
stack.push(p);//把当前节点保存到堆栈中,以便遍历完左子树后遍历右子树
p = p.left;
}
}
//最后一个叶子节点输出后,在栈中取该节点的根节点,遍历这个根节点的右孩子。
if (!stack.isEmpty()) {
Btree<T> pop = stack.pop();
p = pop.right;
}
}
2.中序遍历二叉树的非递归实现
思路:
1.根节点入栈,进入其左子树,进而左子树的根节点入栈,进入下一层左子树,….,如此重复,直至当前节点为空·。
2.若栈非空,则退栈,访问出栈节点,并进入其右子树。
代码:
/*
* 中根序遍历
*/
public void midOrder(Btree<T> root){
Stack<Btree<T>> stack = new Stack<Btree<T>>();
Btree<T> p = root;//保存当前节点
while(p != null || !stack.isEmpty()){
if (p != null) {
stack.push(p);//当前节点入栈
p = p.left;//遍历他的左节点
}else{
p = stack.pop();//当前节点为空,则取出栈顶元素,
visitDate(p);//打印栈顶元素
p = p.right;//遍历该节点的右节点
}
}
}
3.后序遍历二叉树的非递归实现
思路:
1.根节点入栈,进入其左子树,进而左子树的根节点入栈,进入下一层左子树,……,如此重复,直至当前节点为空。
2.若栈非空,如果栈顶节点p的右子树为空,或者p的右孩子是刚访问的节点q,则退栈、访问p节点,并将p置为空,如果栈顶节点p有右子树且右子树为访问,则进入p的右子树。
我的代码:
/*
* 后续遍历
*/
public void lastOrder(Btree<T> root){
Stack<Btree<T>> stack = new Stack<Btree<T>>();
Btree<T> p = root;//保存当前节点
Btree<T> q = null;//记录最后一个输出的节点,用于检验是不是根节点的右节点
while(p != null || !stack.isEmpty()){
//由根节点向下遍历,知道找到该根节点下的最后一个叶子节点
while (p != null) {
stack.push(p);//非叶子节点入栈
p = p.left;//指向该节点的左孩子
}
//p为空,栈非空,说明遍历完了左孩子,处于叶子节点状态
if (!stack.isEmpty()) {
p = stack.pop();//栈顶出栈
//因为如果该节点有右节点,肯定是先访问完右节点才开始访问跟节点的
//p.right == null:表示没有右节点,可以直接访问根节点
//p.right == q:刚访问完该节点右节点,则可以访问我该节点
if (p.right == null || p.right == q) {
visitDate(p);//访问当前节点
q = p;//记录这个节点
p = null;
}else{//开始遍历右孩子
p = p.right;
}
}
}
}
非递归算法的时间复杂度:每个节点都是出栈一次,入栈一次,每个节点访问一次,对于有n个节点的二叉树,设访问每个节点是轻量级的,则上述二叉树的非递归遍历算法的时间复杂度是O(n).
非递归算法的空间复杂度:对于深度为k的二叉树,上述四个算法所需的栈空间与二叉树的深度k成正比,因此空间复杂度是O(k).