遍历二叉树
来源:互联网 发布:瞻博网络中国裁员 编辑:程序博客网 时间:2024/05/21 02:51
在二叉树的一些应用中,常常要求在树中查找具有某些特征的结点,或者对树中全部结点逐一进行某种处理。这就提出了一个遍历二叉树的问题,即如果按某条搜索路径巡访树中的每个结点,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义很广,可以是对结点作各种处理,如输出结点的信息等。遍历对于线性结构来说是一个很容易解决的问题,而对二叉树则不然,因为二叉树是一种非线性结构。
回顾二叉树的定义,我们知道二叉树可以看成是由三个部分组成的:一个根结点,根的左子树和根的右子树。因此如果能够遍历这三部分,则可以遍历整棵二叉树。如果用L、D、R分别表示遍历左子树、访问根结点、遍历右子树。那么对于二叉树的遍历次序就可以有6中方案,即L、D、R的排列组合,那么如果限制对左子树的遍历要先于对右子树的遍历,这就剩下3中情况,分别是:
- (1)访问根,遍历左子树,遍历右子树(DLR)
- (2)遍历左子树,访问根,遍历右子树(LDR)
- (3)遍历左子树,遍历右子树,访问根(LRD)
根据对根访问的不同顺序,分别成DLR为先根(序)遍历,LDR为中根(序)遍历,LRD为后根(序)遍历。
需要注意的是,这里的先序遍历、中序遍历和后序遍历是递归定义的,即在左右子树中也是按相应的规律进行遍历。
例如,如上图所示的二叉树表示下述表达式:
4 + (6 - 8 + 2 * 2)*2
若先序遍历此二叉树,按访问结点的先后次序将结点排列起来,可得二叉树的先序序列为:
+ 4 * + - 6 8 * 2 2 2 (式1)
类似的,中序遍历此二叉树,可得此二叉树的中序序列为:
4 + 6 - 8 + 2 * 2 * 2 (式2)
后序遍历此二叉树,可得此二叉树的后序序列为:
4 6 8 - 2 2 * + 2 * + (式3)
从表达式上看,式1、2、3恰好是表达式的前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。
我们可以很简单地写出遍历二叉树的递归算法,同时,根据递归时工作栈的状态变化情况,我们也可以很简单地写出遍历二叉树的非递归算法。
每一个遍历的非递归算法都有两种思路,如果借助访问标记的话,我们可以根据递归的特点,手动地用一个栈来保存每一个结点,举例来说,先序遍历时先访问根,再访问左子树,接着访问右子树,即DLR,那么我们对于每一个结点,都按照RLD的顺序入栈,如果一个结点的左右子树已经入过栈,那么该结点视为已访问过的,下一次再遇到的话直接从栈中弹出,并且访问即可;而如果一个结点是未访问过的,则先将其弹出,再按照RLD的顺序,将其右孩子、左孩子入栈,接着自己再入栈。重复这个过程直到栈为空。这种方法实现和理解起来都比较简单。
另一种思路不需要借助访问标记,但是也要借助一个栈来实现,这种方式理解起来比上一种方法稍微有点复杂。
那么下述代码,实现了二叉树的递归与非递归的遍历方法。并且,每一种非递归方法都采用至少两种思路实现。
另外,我们是通过二叉树的顺序存储结构(即数组)来创建二叉树,与此同时,也实现了二叉树的层序遍历。
import java.util.HashMap;import java.util.Map;import com.gavin.datastructure.queue.ArrayQueue;import com.gavin.datastructure.stack.ArrayStack;/** * 实现二叉树的构建以及各种遍历 * * @author Gavin * * @param <T> */public class BinaryTree<T> { /** * 从顺序存储结构,即数组中构建二叉树 * * @param array * 数组 * @return 返回二叉树根结点 */ public TripleLinkedNode<T> createTreeFromArray(T[] array) { if (array == null || array.length == 0) { return null; } Map<Integer, TripleLinkedNode<T>> nodeMap = new HashMap<>(); for (int i = 0; i < array.length; i++) { // 如果值为空,说明当前结点不存在 if (array[i] == null) { continue; } TripleLinkedNode<T> currentNode = null; if (nodeMap.containsKey(i)) { // 当前结点已存在 currentNode = nodeMap.get(i); } else { // 当前结点不存在 currentNode = new TripleLinkedNode<T>(array[i]); // 加入 nodeMap.put(i, currentNode); } // 设置当前结点的左孩子 if (2 * i + 1 < array.length && array[2 * i + 1] != null) { TripleLinkedNode<T> leftNode = new TripleLinkedNode<T>(array[2 * i + 1]); currentNode.setLeft(leftNode); nodeMap.put(2 * i + 1, leftNode); } // 设置当前结点的右孩子 if (2 * i + 2 < array.length && array[2 * i + 2] != null) { TripleLinkedNode<T> rightNode = new TripleLinkedNode<T>(array[2 * i + 2]); currentNode.setRight(rightNode); nodeMap.put(2 * i + 2, rightNode); } } // 返回根结点root return nodeMap.get(0); } /** * 访问一个结点 * * @param node */ public void visit(TripleLinkedNode<T> node) { System.out.print(node.getData() + " "); } /** * 将访问标记复位为false * * @param root */ public void resetVisited(TripleLinkedNode<?> root) { if (root == null) { return; } root.setVisited(false); resetVisited(root.getLeft()); resetVisited(root.getRight()); } /** * 递归实现前序遍历 * * @param root */ public void preOrderTraverse(TripleLinkedNode<T> root) { if (root == null) { return; } visit(root); preOrderTraverse(root.getLeft()); preOrderTraverse(root.getRight()); } /** * 递归实现后续遍历 * * @param root */ public void postOrderTraverse(TripleLinkedNode<T> root) { if (root == null) { return; } postOrderTraverse(root.getLeft()); postOrderTraverse(root.getRight()); visit(root); } /** * 递归实现中序遍历 * * @param root */ public void inOrderTraverse(TripleLinkedNode<T> root) { if (root == null) { return; } inOrderTraverse(root.getLeft()); visit(root); inOrderTraverse(root.getRight()); } /** * 层序遍历 * * @param root */ public void levelOrderTraverse(TripleLinkedNode<T> root) { // 要使用一个队列 ArrayQueue<TripleLinkedNode<T>> queue = new ArrayQueue<>(); queue.enqueue(root); while (!queue.isEmpty()) { TripleLinkedNode<T> currentNode = queue.dequeue(); // 访问队头结点 visit(currentNode); // 左孩子入队 if (currentNode.hasLeft()) { queue.enqueue(currentNode.getLeft()); } // 右孩子入队 if (currentNode.hasRight()) { queue.enqueue(currentNode.getRight()); } } } /** * 使用访问标记的非递归前序遍历 * * @param root */ public void preOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) { ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; stack.push(node); while (!stack.isEmpty()) { node = stack.peek(); if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) { // 已被访问过或者是叶子结点,则直接访问 visit(node); stack.pop(); continue; } // 先弹出 stack.pop(); if (node.hasRight()) { stack.push(node.getRight()); } if (node.hasLeft()) { stack.push(node.getLeft()); } // 再加入 node.setVisited(true); stack.push(node); } } /** * 不使用访问标记的非递归实现前序遍历 * * @param root */ public void preOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) { // 要用到一个栈 ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; while (node != null) { // 向左走到底 while (node != null) { visit(node); // 访问根结点 // 将该根结点的右孩子入栈 if (node.hasRight()) { stack.push(node.getRight()); } node = node.getLeft(); } // 右子树根退栈遍历右子树 if (!stack.isEmpty()) { node = stack.pop(); } } } /** * 不使用访问标记的非递归实现前序遍历2:简单好理解 * * @param root */ public void preOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) { // 要用到一个栈 ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; stack.push(node); while (!stack.isEmpty()) { node = stack.pop(); visit(node); if (node.hasRight()) { stack.push(node.getRight()); } if (node.hasLeft()) { stack.push(node.getLeft()); } } } /** * 使用访问标记的非递归中序遍历 * * @param root */ public void inOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) { ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; stack.push(node); while (!stack.isEmpty()) { node = stack.peek(); if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) { visit(node); stack.pop(); continue; } // 先弹出 stack.pop(); if (node.hasRight()) { stack.push(node.getRight()); } // 再加入 node.setVisited(true); stack.push(node); if (node.hasLeft()) { stack.push(node.getLeft()); } } } /** * 不使用访问标记的非递归实现中序遍历 * * @param root */ public void inOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) { // 要用到一个栈 ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; while (node != null || !stack.isEmpty()) { // 向左走到尽头 while (node != null) { stack.push(node); node = node.getLeft(); } if (!stack.isEmpty()) { // 取出根结点并访问 node = stack.pop(); visit(node); // 转向根的右子树进行遍历 node = node.getRight(); } } } /** * 使用访问标记的非递归后序遍历 * * @param root */ public void postOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) { // 要用到一个栈 ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; stack.push(node); while (!stack.isEmpty()) { node = stack.peek(); // 如果曾经已经访问过其孩子,这里直接访问即可。或者是叶子结点的话,这里也直接访问 if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) { // 叶子结点 visit(node); stack.pop(); continue; } // 先加入右孩子 if (node.hasRight()) { stack.push(node.getRight()); } // 再加入左孩子 if (node.hasLeft()) { stack.push(node.getLeft()); } // 其子结点加入栈,这里设置其访问标记为true node.setVisited(true); } } /** * 不使用访问标记的非递归后序遍历 * * @param root */ public void postOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) { ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); TripleLinkedNode<T> node = root; while (node != null || !stack.isEmpty()) { // 先左后右不断深入 while (node != null) { // 将根结点入栈 stack.push(node); if (node.hasLeft()) { node = node.getLeft(); } else { node = node.getRight(); } } if (!stack.isEmpty()) { // 取出栈顶元素访问 node = stack.pop(); visit(node); } // 满足条件时,说明栈顶根结点右子树已经访问过,应出栈访问之 while (!stack.isEmpty() && stack.peek().getRight() == node) { node = stack.pop(); visit(node); } // 转向栈顶根结点的右子树继续后续遍历 if (!stack.isEmpty()) { node = stack.peek().getRight(); } else { node = null; } } } /** * 不使用访问标记的非递归后序遍历的第二种解法:使用两个栈,简单也好理解 * * @param root */ public void postOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) { ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>(); ArrayStack<TripleLinkedNode<T>> output = new ArrayStack<>(); TripleLinkedNode<T> node = root; stack.push(node); while (!stack.isEmpty()) { node = stack.pop(); output.push(node); if (node.hasLeft()) { stack.push(node.getLeft()); } if (node.hasRight()) { stack.push(node.getRight()); } } while (!output.isEmpty()) { visit(output.pop()); } }}
下面代码对当前的二叉树遍历进行测试:
import static org.junit.Assert.*;import org.junit.Test;public class BinaryTreeTest { @Test public void testCreateTreeFromArray() { BinaryTree<String> bt = new BinaryTree<>(); String[] array = { "+", "4", "*", null, null, "+", "2", null, null, null, null, "-", "*", null, null, null, null, null, null, null, null, null, null, "6", "8", "2", "2", null, null, null, null }; TripleLinkedNode<String> root = bt.createTreeFromArray(array); assertEquals(11, root.getSize()); assertEquals(5, root.getHeight()); // 遍历 System.out.println("递归先序遍历:"); bt.preOrderTraverse(root); System.out.println(); System.out.println("使用访问标记的非递归先序遍历:"); bt.preOrderTraverseNoRecursionWithVisited(root); System.out.println(); System.out.println("不使用访问标记的非递归先序遍历1:"); bt.preOrderTraverseNoRecursionWithoutVisited(root); // 标记复位 bt.resetVisited(root); System.out.println(); System.out.println("不使用访问标记的非递归先序遍历2:"); bt.preOrderTraverseNoRecursionWithoutVisited2(root); System.out.println(); System.out.println(); System.out.println("递归中序遍历:"); bt.inOrderTraverse(root); System.out.println(); System.out.println("使用访问标记的非递归中序遍历:"); bt.inOrderTraverseNoRecursionWithVisited(root); System.out.println(); System.out.println("不使用访问标记的非递归中序遍历:"); bt.inOrderTraverseNoRecursionWithoutVisited(root); //标记复位 bt.resetVisited(root); System.out.println(); System.out.println(); System.out.println("递归后序遍历:"); bt.postOrderTraverse(root); System.out.println(); System.out.println("使用访问标记的非递归后序遍历:"); bt.postOrderTraverseNoRecursionWithVisited(root); System.out.println(); System.out.println("不使用访问标记的非递归后序遍历1:"); bt.postOrderTraverseNoRecursionWithoutVisited(root); bt.resetVisited(root); System.out.println(); System.out.println("不使用访问标记的非递归后序遍历2:"); bt.postOrderTraverseNoRecursionWithoutVisited2(root); System.out.println(); System.out.println(); System.out.println("层序遍历:"); bt.levelOrderTraverse(root); }}
输出如下:
递归先序遍历:+ 4 * + - 6 8 * 2 2 2 使用访问标记的非递归先序遍历:+ 4 * + - 6 8 * 2 2 2 不使用访问标记的非递归先序遍历1:+ 4 * + - 6 8 * 2 2 2 不使用访问标记的非递归先序遍历2:+ 4 * + - 6 8 * 2 2 2 递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 使用访问标记的非递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 不使用访问标记的非递归中序遍历:4 + 6 - 8 + 2 * 2 * 2 递归后序遍历:4 6 8 - 2 2 * + 2 * + 使用访问标记的非递归后序遍历:4 6 8 - 2 2 * + 2 * + 不使用访问标记的非递归后序遍历1:4 6 8 - 2 2 * + 2 * + 不使用访问标记的非递归后序遍历2:4 6 8 - 2 2 * + 2 * + 层序遍历:+ 4 * + 2 - * 6 8 2 2
可见,每个算法的结果都完全正确。
- 二叉树遍历、分层遍历
- 遍历二叉树--二叉树
- 【树】遍历二叉树
- 二叉树遍历
- 二叉树遍历
- 二叉树的遍历
- (原创)遍历二叉树
- 二叉树遍历-php
- 查找--遍历二叉树
- 遍历二叉树
- 二叉树遍历规则
- 二叉树的遍历
- 二叉树遍历
- 遍历二叉树
- 二叉树遍历问题
- 二叉树遍历
- 二叉树遍历(zz)
- 二叉树的遍历
- 事件传递机制
- 机器学习中为什么需要对数据进行归一化?
- 利用python将多份excel表格整理成一份新表格
- 不同Content Type下的$.ajax请求
- [编辑器] MarkDown 编辑器的常用命令总结
- 遍历二叉树
- plusOne
- 杭电 oj 题目4414 Finding crosses
- Bean的装配(1)
- if和与、或的组合判断
- 变量+标量变量+变量命名规则+变量命名规则
- 存储结构的定义及其分类
- ASP.NET页面输出XML(C#)
- .NET 在线生成XML文档,并提供下载