1、二叉树的遍历
在计算机程序中,遍历本身是一个线性操作,所以遍历同样具有线性结构的数组或链表是一件轻而易举的事情,如下图:
二叉树则是典型的非线性数据结构,遍历时需要把非线性关联的节点转化成一个线性的序列,以不同的方式来遍历,遍历出的序列顺序也不同:
二叉树遍历分类
从节点之间位置关系的角度来看,二叉树的遍历分为4种:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
进一步可分为两大类:
- 深度优先遍历(前序遍历、中序遍历、后序遍历)
- 广度优先遍历(层序遍历)
2、深度优先遍历
深度优先和广度优先这两个概念不止局限于二叉树,它们更是一种抽象的算法思想,决定了访问某些复杂数据结构的顺序,深度优先:就是偏向于纵深,“一头扎到底”的访问方式!
【1】前序遍历
二叉树的前序遍历,输出顺序是根节点、左子树、右子树,如下图就是一个二叉树的前序遍历:
每个节点左侧的序号代表该节点的输出顺 序,详细步骤如下:
- 首先输出的是根节点1
- 由于根节点1存在左孩子,输出左孩子节点2
- 由于节点2也存在左孩子,输出左孩子节点4
- 节点4既没有左孩子,也没有右孩子,那么回到节点2,输出节点2的右孩子节点5
- 节点5既没有左孩子,也没有右孩子,那么回到节点1,输出节点1的右孩子节点3
- 节点3没有左孩子,但是有右孩子,因此输出节点3的右孩子节点6
到此为止,所有的节点都遍历输出完毕
【2】中序遍历
二叉树的中序遍历,输出顺序是左子树、根节点、右子树,如下图就是一个二叉树的中序遍历:
每个节点左侧的序号代表该节点的输出顺 序,详细步骤如下:
- 首先访问根节点的左孩子,如果这个左孩子还拥有左孩子,则继续深入访问下去,一直找到不再有左孩子的节点,并输出该节点。显然,第一个没有左孩子的节点是节点4
- 依照中序遍历的次序,接下来输出节点4的父节点2
- 再输出节点2的右孩子节点5
- 以节点2为根的左子树已经输出完毕,这时再输出整个二叉树的根节点1
- 由于节点3没有左孩子,所以直接输出根节点1的右孩子节点3
- 最后输出节点3的右孩子节点6
到此为止,所有的节点都遍历输出完毕
【3】后序遍历
二叉树的后序遍历,输出顺序是左子树、右子树、根节点,下图就是一个二叉树的后序遍历
后序遍历的核心思想同上,在此不再赘述,具体实现代码如下:
//构建二叉树
public class TreeNode2 {
public static TreeNode createBinaryTree(LinkedList<Integer>inputList){
TreeNode node = null;
if(inputList==null || inputList.isEmpty()){
return null;
}
Integer data = inputList.removeFirst();
if(data != null){
node = new TreeNode(data);
node.leftChild = createBinaryTree(inputList);
node.rightChild = createBinaryTree(inputList);
}
return node;
}
//二叉树前序遍历
public static void preOrderTraveral(TreeNode node){
if(node == null){
return;
}
System.out.println(node.data);
preOrderTraveral(node.leftChild);
preOrderTraveral(node.rightChild);
}
//二叉树中序遍历
public static void inOrderTraveral(TreeNode node){
if(node == null){
return;
}
inOrderTraveral(node.leftChild);
System.out.println(node.data);
inOrderTraveral(node.rightChild);
}
//二叉树后序遍历
public static void postOrderTraveral(TreeNode node){
if(node == null){
return;
}
postOrderTraveral(node.leftChild);
postOrderTraveral(node.rightChild);
System.out.println(node.data);
}
// 二叉树节点
private static class TreeNode {
int data;
TreeNode leftChild;
TreeNode rightChild;
TreeNode(int data) {
this.data = data;
}
}
public static void main(String[] args) {
LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4}));
TreeNode treeNode = createBinaryTree(inputList);
System.out.println(" 前序遍历:");
preOrderTraveral(treeNode);
System.out.println(" 中序遍历:");
inOrderTraveral(treeNode);
System.out.println(" 后序遍历:");
postOrderTraveral(treeNode);
}
编译输出:
这3种遍历方式的区别:仅仅是输出的执行位置不同:前序遍历的输出在前,中序遍历的输出在中间,后序遍历的输出在最后;在代码的main函数中,通过{3,2,9,null,null,10,null,null,8,null,4}这 样一个线性序列,构建成的二叉树如下:
【4】栈实现二叉树的非递归遍历
绝大多数可以用递归解决的问题都可以用 ‘栈’ 来解决,因为递归和栈都有回溯的特性;
下面以二叉树的前序遍历为例展示具体过程:
- 1. 首先遍历二叉树的根节点1,放入栈中
- 2. 遍历根节点1的左孩子节点2,放入栈中
- 3. 遍历节点2的左孩子节点4,放入栈中
- 节点4既没有左孩子,也没有右孩子,需要回溯到上一个节点2。可是现在并不是做递归操作,栈已经存储了刚才遍历的路径;让旧的栈顶元素4出栈,就可以重新访问节点2,得到节点2的右孩子节点5;此时节点2已经没有利用价值(已经访问过左孩子和右孩子),节点2出栈,节点5入栈
- 节点5既没有左孩子,也没有右孩子,需要再次回溯,一直回溯到节点 1;所以让节点5出栈,根节点1的右孩子是节点3,节点1出栈,节点3入栈
- 6. 节点3的右孩子是节点6,节点3出栈,节点6入栈
- 7.节点6既没有左孩子,也没有右孩子,所以节点6出栈。此时栈为空,遍历结束
—————————————————————————————————————————————
内容来源:《漫画算法》