本文正在参加「金石计划 . 瓜分6万现金大奖」
二叉树的遍历
深度优先遍历
前序遍历
前序遍历其检索顺序为根节点、左孩子节点、右孩子节点。
所以检索结果为:5 、2 、1、3、6、7
中序遍历
中序遍历其检索顺序为左孩子节点、根节点、右孩子节点。
所以检索结果为:1、2、3、5、6、7
后序遍历
后续遍历其检索顺序为左孩子节点、右孩子节点、根节点。
所以检索结果为:1、3、2、7、6、5
递归代码实现
首先定义二叉树节点对象
/**
* 二叉树节点对象
*/
class TreeNode{
Object data;
TreeNode leftNode;
TreeNode rightNode;
public TreeNode(Object data){
this.data = data;
}
@Override
public String toString() {
return "TreeNode{" +
"data=" + data +
", leftNode=" + leftNode +
", rightNode=" + rightNode +
'}';
}
}
定义二叉树构建方法,这里沿用递归逻辑
/**
* 创建二叉树,如果节点不包含左右子树,那么节点置为null占位
* @param list
* @return
*/
public static TreeNode createBinaryTree(LinkedList<Object> list){
if (list == null || list.isEmpty()){
return null;
}
TreeNode treeNode = null;
Object data = list.removeFirst();
if (data != null){
treeNode = new TreeNode(data);
// 如果没有遇到节点为null的会一直给左节点赋值(类似递归)
treeNode.leftNode = createBinaryTree(list);
treeNode.rightNode = createBinaryTree(list);
}
return treeNode;
}
假如存在二叉树如下
那么构建节点的list传参应该如下所示
{3, 2, 9, null, null, 10, null, null, 8, null, 4}
遍历代码
/**
* 前序遍历遍历顺序(根节点、左孩子节点、右孩子节点)
* @param treeNode
*/
public static void preOrderTraverse(TreeNode treeNode){
if (treeNode == null){
return;
}
System.out.print(treeNode.data+"\t");
preOrderTraverse(treeNode.leftNode);
preOrderTraverse(treeNode.rightNode);
}
/**
* 中序遍历遍历顺序(左孩子节点、根节点、右孩子节点)
* @param treeNode
*/
public static void inOrderTraverse(TreeNode treeNode){
if (treeNode == null){
return;
}
inOrderTraverse(treeNode.leftNode);
System.out.print(treeNode.data+"\t");
inOrderTraverse(treeNode.rightNode);
}
/**
* 后序遍历遍历顺序(左孩子节点、右孩子节点、根节点)
* @param treeNode
*/
public static void postOrderTraverse(TreeNode treeNode){
if (treeNode == null){
return;
}
postOrderTraverse(treeNode.leftNode);
postOrderTraverse(treeNode.rightNode);
System.out.print(treeNode.data+"\t");
}
测试代码
public static void main(String[] args) {
List<Object> objectList = Arrays.asList(new Object[]
{3, 2, 9, null, null, 10, null, null, 8, null, 4});
LinkedList<Object> list = new LinkedList<>(objectList);
TreeNode binaryTree = createBinaryTree(list);
System.out.println(binaryTree);
// 前序遍历 3 2 9 10 8 4
preOrderTraverse(binaryTree);
System.out.println();
// 中序遍历 9 2 10 3 8 4
inOrderTraverse(binaryTree);
System.out.println();
// 后序遍历 9 10 2 4 8 3
postOrderTraverse(binaryTree);
}
栈遍历二叉树代码实现
除了递归其实我们可以拓展一种解决思路,递归的目的是回溯,通过回溯才能完成二叉树的遍历,但其实除了递归有此特性,栈同样可以,栈可以利用其先入后出的特性实现回溯的效果。
以前序遍历为例,存在如下二叉树
先将节点3入栈,节点3存在左节点2,所以将节点2入栈,节点2存在左节点9所以节点9入队。
当遍历到节点9发现没有左孩子节点,而且不存在右孩子节点,所以需要回溯到节点9的父节点2,将节点9出队,访问节点2,同时得到节点2的右孩子节点5,这时节点2没有存在价值,会将节点2出队,同时将节点5入队
遍历到节点5发现又没有左孩子节点以及右孩子节点,这时需要回溯到节点5的父节点3,通过节点3得到其右孩子节点8,节点3同样无意义将节点3出队,同时节点8入队
遍历节点8发现其只存在右孩子节点4,将节点8出队节点4入队。
最后遍历节点4因为不存在左孩子节点和右孩子节点,节点4出队,栈为空完成遍历。
按照这个思路得到前序遍历如下代码
/**
* 前序遍历,采用栈的回溯性遍历二叉树
* @param root
*/
public static void preOrderTraverseByStack(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode treeNode = root;
while (treeNode!=null || !stack.isEmpty()){
while (treeNode!=null){
stack.push(treeNode);
System.out.print(treeNode.data+"\t");
treeNode = treeNode.leftNode;
}
if (!stack.isEmpty()){
TreeNode treeNode1 = stack.pop();
treeNode = treeNode1.rightNode;
}
}
}
中序遍历只是打印位置不一样,如下
/**
* 中序栈遍历 采用栈的回溯性
* @param root
*/
public static void inOrderTraverseByStack(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode treeNode = root;
while (treeNode!=null || !stack.isEmpty()){
while (treeNode!=null){
stack.push(treeNode);
treeNode = treeNode.leftNode;
}
if (!stack.isEmpty()){
TreeNode treeNode1 = stack.pop();
System.out.print(treeNode1.data+"\t");
treeNode = treeNode1.rightNode;
}
}
}
需要注意的是后续遍历及其特殊,后续遍历的顺序是左孩子节点、右孩子节点、父节点,在打印时需要判断当前节点是左孩子节点还是右孩子节点,如果为左孩子节点那么需要先访问右孩子节点再回来访问根节点,如果为右孩子节点就可以直接访问根节点。
/**
* 后序遍历 栈回溯
* @param root
*/
public static void postOrderTraverseByStack(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
TreeNode treeNode = root;
TreeNode flag = null;
while (treeNode!=null || !stack.isEmpty()){
while (treeNode!=null){
stack.push(treeNode);
treeNode = treeNode.leftNode;
}
// 不出队,只取值
treeNode = stack.peek();
/**
* flag存放上一次访问节点
* 用于判断上一次访问节点是左孩子节点还是右孩子节点
* 若是左孩子节点,则需要跳过根节点,先进入右孩子节点,再回头访问根节点
* 若是右孩子节点,则直接访问根节点
*
* 会访问两次根节点,第一次是借助根节点访问右孩子节点,如果右孩子节点已经被访问就打印根节点
*/
if (treeNode.rightNode==null || flag == treeNode.rightNode){
flag = stack.pop();
System.out.print(treeNode.data+"\t");
treeNode = null;
}else {
treeNode = treeNode.rightNode;
}
}
}
广度优先遍历
广度优先遍历就是一层一层访问,例如存在二叉树
从根节点往下一层一层遍历,那么遍历结果为
3、2、8、9、10、4
这里实现方式同样可以使用递归实现,但这里拓展以下思路,这里采用队列实现
-
根节点3入队
-
节点3出队,输出节点3,并得到节点3的左右孩子节点2,8入队。
-
节点2出队,输出节点2,并得到节点2的左右孩子节点9,10入队。
-
节点8出队,输出节点8,并得到节点8的右孩子节点4入队。
-
节点9出队,输出节点9,其左右孩子节点都为空,不入队(节点10,节点4类似)。
-
队列为空程序结束。
队列遍历二叉树代码实现
/**
* 层序遍历 一层一层遍历采用队列实现
* @param root
*/
public static void levelOrderTraversal(TreeNode root){
Queue<TreeNode> queue = new LinkedBlockingQueue<>();
queue.offer(root);
while (!queue.isEmpty()){
// peek检索但不删除 poll检索并删除 remove删除
// offer插入 put 插入
TreeNode treeNode = queue.poll();
System.out.print(treeNode.data+"\t");
if (treeNode.leftNode!=null){
queue.offer(treeNode.leftNode);
}
if (treeNode.rightNode!=null){
queue.offer(treeNode.rightNode);
}
}
}