一、之前给出的二叉树遍历算法都采用的是递归算法。递归算法虽然结构简洁,但在时空开销上相对较大,从而导致运行效率较低,并且有有些程序设计环境不支持递归,这就要求将递归算法转换成非递归算法。
二、将递归算法转换为非递归算法有两种方式:一种是直接转换法,不需要回溯;另一种是间接转换法,需要回溯。直接转换法使用一些变量保存中间结果,递归过程用循环结构来替代;而间接转换法需要利用栈保存中间结果,故引入一个栈结构,并按照递归算法执行过程中编译栈的工作原理,得到相应的非递归算法。二叉树遍历操作的非递归实现采用的就是间接转换法。
三、二叉树非递归遍历算法
以下面的二叉树为例:
1.先序非递归遍历算法(ABDHKECFIGJ)(中-左-右)
(1)先序非递归遍历过程要借助一个栈来记载当前被访问结点的右孩子结点,以便遍历完一个结点的左子树后能顺利地进入这个结点的右子树继续进行遍历。
主要思想是:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点就先访问该结点,并将该结点的非空右孩子结点压栈。当左子树结点访问完成后,从栈顶弹出结点的右孩子结点,然后用上述同样的方法去遍历该结点的右子树,依次类推,直到二叉树中所有的结点都被访问为止。
(2)操作过程(根结点入栈--栈顶出栈并访问结点--访问非空左孩子--入栈非空右孩子)
- 创建一个栈对象,根结点入栈
- 当栈为非空时,将栈顶结点弹出栈内并访问该结点
- 对当前访问结点的非空左孩子结点相继依次访问,并将当前访问结点的非空右孩子结点压入栈内
- 重复执行第2步和第3步,直到栈为空为止。
(3)实现
// 先序非递归遍历算法 public void preOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkStack stack = new LinkStack(); // 构造栈对象 stack.stackPush(T); // 根结点先入栈 while (!stack.isStackEmpty()) { // 栈不为空时 T = (BiTreeNode) stack.stackPop(); System.out.print(T.data); // 访问结点 while (T != null) { if (T.lchild != null) System.out.println(T.lchild.data); // 访问结点的非空左孩子 if (T.rchild != null) stack.stackPush(T.rchild); // 将结点的非空右孩子入栈 T = T.lchild; // 转换为原结点的非空左孩子 } } } }
实现过程:
T为A,T非空-T入栈-栈非空-A出栈并打印-B打印C入栈-T为B-D打印E入栈-T为D-H打印-T为H-K入栈-H左空-K出栈并打印-K左空右空-E出栈并打印...
2.中序非递归遍历算法(HKDBEAIFCGJ)(左-中-右)
(1)中序非递归遍历过程也要借助一个栈来记载遍历过程中所经历的而未被访问的所有结点,以便遍历完一个结点的左子树后顺利地返回到它的父结点。
基本思想是:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中将所遇到的每一个结点依次压栈,直到二叉树中最左下角结点压栈为止,然后从栈中弹出栈顶结点并对其进行访问,访问完后再进入该结点的右子树,并用上述同样的方法去遍历该结点的右子树,依次类推,直到二叉树中所有的结点都被访问为止。
(2)操作过程
- 创建一个栈对象,根结点入栈
- 若栈非空,则将栈顶结点的非空左孩子相继入栈
- 栈顶结点出栈并访问非空栈顶结点,并使该栈顶结点的非空右孩子结点入栈
- 重复第2步和第3步,直到栈为空为止
(3)实现
// 中序非递归遍历算法
public void inOrderTraverse() throws Exception{
BiTreeNode T = root;
if (T != null) {
LinkStack stack = new LinkStack(); // 构造栈对象
stack.stackPush(T); // 根结点先入栈
while (!stack.isStackEmpty()) {
while (stack.getTopElem() != null) // 栈顶非空结点左孩子相继入栈
stack.stackPush(((BiTreeNode)stack.getTopElem()).lchild);
stack.stackPop(); // 如果没有左孩子或右孩子,将空的左孩子或右孩子出栈
if (!stack.isStackEmpty()) {
T = (BiTreeNode) stack.stackPop(); // 栈顶结点出栈
System.out.print(T.data); // 访问栈顶结点
stack.stackPush(T.rchild); // 出栈的栈顶结点的右孩子入栈
}
}
}
}
实现过程:
T为A-A不为空-A入栈(A)
-1栈不为空-栈顶元素不为空-B入栈(B A)-栈顶元素不为空-D入栈(D B A)-栈顶元素不为空-H入栈(H D B A)-栈顶元素不为空-H左入栈(空 H D B A)-空出栈(H D B A)-
-2栈不为空-H出栈T为H(D B A)-打印H-k入栈(K D B A)
—1栈不为空-栈顶元素不为空-k左入栈(空 K D B A)-空出栈
-2栈不为空-K出栈T为K(D B A)-打印K-K右入栈(空 D B A)
-1栈不为空--栈顶元素为空-空出栈(D B A)
-2栈不为空-D出栈T为D(B A)-打印D-D右入栈...
3.后序非递归遍历算法(KHDEBIFJGCA)
(1)由于后序遍历是先处理左子树,后处理右子树,最后才访问根结点,所以在遍历搜索过程中也是从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点判断该结点是否第一次经过,若是,则不立即返回,而是将该结点入栈保存,遍历该结点的左子树;当左子树遍历完毕后再返回到该结点,这是还不能访问该结点,而是应该继续进入该结点的右子树遍历;当左右子树均遍历完毕后,才能从栈顶弹出该结点并访问它。
由于在决定栈顶元素是否能够访问时,需要知道该结点的右子树是否被遍历完毕,因此为解决这个问题,在算法中引入一个布尔型的访问变量flag和一个结点指针p,其中,flag用来标记当前栈顶结点是否被访问,当值为true时,表示栈顶结点已被访问;当值为false时,表示当前栈顶结点未被访问,指针p指向当前遍历过程中最后一个被访问的结点。若当前栈顶结点的右孩子结点是空,或者就是p指向的结点,则表明当前结点的右子树已经遍历完毕,此时就可以访问当前栈顶结点。
(2)操作过程
- 创建一个栈对象,根结点进栈,p赋初始值为null
- 若栈非空,则栈顶结点的非空左孩子相继入栈
- 若栈非空,查看栈顶结点,若栈顶结点的右孩子为空,或者与p相等,则将栈顶元素弹出栈并访问它,同时使p指向该结点,并置flag值为true;否则,将栈顶结点的右孩子入栈,并置flag值为false
- 若flag值为true,则重复执行第3步;否则,重复执行第2步和第3步,直到栈为空为止。
(3)实现
// 后序非递归遍历算法 public void postOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkStack stack = new LinkStack(); // 构造栈对象 stack.stackPush(T); // 根结点先入栈 Boolean flag; BiTreeNode p = null; while (!stack.isStackEmpty()) { while (stack.getTopElem() != null) // 栈顶非空结点左孩子相继入栈 stack.stackPush(((BiTreeNode)stack.getTopElem()).lchild); stack.stackPop(); // 如果没有左孩子或右孩子,将空的左孩子或右孩子出栈 while (!stack.isStackEmpty()) { T = (BiTreeNode) stack.getTopElem();// 查看栈顶结点 if (T.rchild == null || T.rchild == p) { System.out.print(T.data); // 访问栈顶结点 stack.stackPop(); // 移除栈顶结点 p = T; // p指向当被访问的结点 flag = true; // 设置已访问标记 } else { stack.stackPush(T.rchild); // 右孩子入栈 flag = false; // 设置未被访问标记 } if (!flag) // 如果未被访问,则break break; } } } }
实现过程:
根结点A不为空-A进栈
-1栈非空-栈顶非空-B进栈-栈顶非空-D进栈-栈顶非空-H进栈-栈顶非空-H左空进栈-栈顶空-空出栈
-2栈非空-T为H-H右非空进入else,K入栈,flag为false,break-
-1栈非空-栈顶非空-k左空入栈-栈顶空-空出栈
-2栈非空-T为K,K右空成立,打印K,K出栈,p为k,flag为true,
-2栈非空-T为H-H右为p为k(说明K已经被访问)-打印H-H出栈-p为H-flag为true...
4.层序非递归遍历算法
(1)层次遍历操作的实现过程中需要一个队列最为辅助的存储结构。在遍历开始时,首先将根结点入队,然后每次从队列中取出首元素进行处理,每处理一个结点,都是先访问该结点,再按从左到右的顺序把它的孩子依次入队。这样,上一层的结点总排在下一层结点的前面,从而实现了二叉树的层次遍历。
(2)操作过程
- 创建一个队列对象,根结点入队
- 若队列非空,则将队首结点出队并访问该结点,再将该结点的非空左、右孩子结点依次入队
- 重复执行第2步,直到队列为空位置。
(3)实现
// 层序非递归遍历算法 public void levelOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkQueue queue = new LinkQueue(); // 构造链队列 queue.queueEnter(T); // 根结点入链队列 while (! queue.isqueueEmpty()) { T = (BiTreeNode) queue.queuePoll(); // 访问结点并出队列 System.out.print(T.data); if (T.lchild != null) // 左孩子非空,入队列 queue.queueEnter(T.lchild); if (T.rchild != null) // 右孩子非空,入队列 queue.queueEnter(T.rchild); } } }
实现过程:
根节点A不为空 - A入队列(A)
- 队列非空 - T为A并且A出队列()— 打印A - B入队列(B) - C入队列(C B)
- 队列非空 - T为B并且B出队列(C) - 打印B - D入队列(D C)- E入队列(E D C)
- 队列非空 - T为C并且C出队列(E D)- 打印C -F入队列(F E D)- G入队列(G F E D)
- 队列非空 - T为D并且出队列(G F E) - 打印D - H入队列(H G F E)
- 队列非空...
四、二叉树非递归遍历算法的Java语言代码实现:
package bigjun.iplab.linkBiTree; import bigjun.iplab.linkQueue.LinkQueue; import bigjun.iplab.linkStack.LinkStack; /** * 二叉树的链式存储结构-二叉链表存储结构 * 前序、中序、后序三种遍历算法的递归实现 */ public class LinkBiTree { private BiTreeNode root; // 构造方法1: 构造一棵空树 public LinkBiTree() { this.root = null; } // 构造方法2: 构造一棵非树 public LinkBiTree(BiTreeNode root) { this.root = root; } // 先序非递归遍历算法 public void preOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkStack stack = new LinkStack(); // 构造栈对象 stack.stackPush(T); // 根结点先入栈 while (!stack.isStackEmpty()) { // 栈不为空时 T = (BiTreeNode) stack.stackPop(); System.out.print(T.data); // 访问结点 while (T != null) { if (T.lchild != null) System.out.print(T.lchild.data); // 访问结点的非空左孩子 if (T.rchild != null) stack.stackPush(T.rchild); // 将结点的非空右孩子入栈 T = T.lchild; // 转换为原结点的非空左孩子 } } } } // 中序非递归遍历算法 public void inOrderTraverse() throws Exception{ BiTreeNode T = root; if (T != null) { LinkStack stack = new LinkStack(); // 构造栈对象 stack.stackPush(T); // 根结点先入栈 while (!stack.isStackEmpty()) { while (stack.getTopElem() != null) // 栈顶非空结点左孩子相继入栈 stack.stackPush(((BiTreeNode)stack.getTopElem()).lchild); stack.stackPop(); // 如果没有左孩子或右孩子,将空的左孩子或右孩子出栈 if (!stack.isStackEmpty()) { T = (BiTreeNode) stack.stackPop(); // 栈顶结点出栈 System.out.print(T.data); // 访问栈顶结点 stack.stackPush(T.rchild); // 出栈的栈顶结点的右孩子入栈 } } } } // 后序非递归遍历算法 public void postOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkStack stack = new LinkStack(); // 构造栈对象 stack.stackPush(T); // 根结点先入栈 Boolean flag; BiTreeNode p = null; while (!stack.isStackEmpty()) { while (stack.getTopElem() != null) // 栈顶非空结点左孩子相继入栈 stack.stackPush(((BiTreeNode)stack.getTopElem()).lchild); stack.stackPop(); // 如果没有左孩子或右孩子,将空的左孩子或右孩子出栈 while (!stack.isStackEmpty()) { T = (BiTreeNode) stack.getTopElem();// 查看栈顶结点 if (T.rchild == null || T.rchild == p) { // 右孩子为空或右子树已经被访问 System.out.print(T.data); // 访问栈顶结点 stack.stackPop(); // 移除栈顶结点 p = T; // p指向当被访问的结点 flag = true; // 设置已访问标记 } else { stack.stackPush(T.rchild); // 右孩子入栈 flag = false; // 设置未被访问标记 } if (!flag) // 如果未被访问,则break break; } } } } // 层序非递归遍历算法 public void levelOrderTraverse() throws Exception { BiTreeNode T = root; if (T != null) { LinkQueue queue = new LinkQueue(); // 构造链队列 queue.queueEnter(T); // 根结点入链队列 while (! queue.isqueueEmpty()) { T = (BiTreeNode) queue.queuePoll(); // 访问结点并出队列 System.out.print(T.data); if (T.lchild != null) // 左孩子非空,入队列 queue.queueEnter(T.lchild); if (T.rchild != null) // 右孩子非空,入队列 queue.queueEnter(T.rchild); } } } // 采用构造方法来构造二叉树 public LinkBiTree createLinkBiTree() { BiTreeNode k = new BiTreeNode('K'); // 要先定义孩子,因为程序是按先后顺序执行的 BiTreeNode i = new BiTreeNode('I'); BiTreeNode j = new BiTreeNode('J'); BiTreeNode e = new BiTreeNode('E'); BiTreeNode h = new BiTreeNode('H', null, k); BiTreeNode f = new BiTreeNode('F', i, null); BiTreeNode g = new BiTreeNode('G', null, j); BiTreeNode d = new BiTreeNode('D', h, null); BiTreeNode c = new BiTreeNode('C', f, g); BiTreeNode b = new BiTreeNode('B', d, e); BiTreeNode a = new BiTreeNode('A', b, c); return new LinkBiTree(a); } public static void main(String[] args) throws Exception { LinkBiTree linkBiTree = new LinkBiTree(); // 创建一棵空树 LinkBiTree biTree = linkBiTree.createLinkBiTree(); // 调用创造方法创造根结点为a的二叉树 System.out.print("先序非递归遍历算法得到的序列为: "); biTree.preOrderTraverse(); System.out.println(); System.out.print("中序非递归遍历算法得到的序列为: "); biTree.inOrderTraverse(); System.out.println(); System.out.print("后序非递归遍历算法得到的序列为: "); biTree.postOrderTraverse(); System.out.println(); System.out.print("层序非递归遍历算法得到的序列为: "); biTree.levelOrderTraverse(); System.out.println(); } }
- 输出:
先序非递归遍历算法得到的序列为: ABDHKECFIGJ
中序非递归遍历算法得到的序列为: HKDBEAIFCGJ
后序非递归遍历算法得到的序列为: KHDEBIFJGCA
层序非递归遍历算法得到的序列为: ABCDEFGHIJK