二叉树的遍历方式主要分为两类:深度优先遍历和广度优先遍历,前者包括前序遍历,中序遍历和后序遍历;后者主要指层序遍历。这篇文章中,我们先对二叉树的创建和每个遍历过程的思路进行分析,然后在文章最后给出一个完整的代码供参考。
目录
二叉树的创建
我们这里用前序序列创建二叉树。
思路:
- 用双向链表存放节点值,包括叶子null节点(可见后面的完整代码部分)
- 先创建根节点,然后以递归方式依次创建左子树和右子树
- 遇到空值(叶子null节点)时,返回null
我们在用递归方式构建二叉树时,最好将每个递归的过程都写在一个框内,方便梳理调用过程。
//以前序方式创建二叉树
//递归实现,将每一个递归的过程写在框中,以便梳理过程
public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
TreeNode node = null;
if(inputList == null || inputList.isEmpty()){
return null;
}
Integer data = inputList.removeFirst();//返回LinkedList的第一个元素,并将头指针移动到下一个节点。
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.print(node.data + " ");
preOrderTraveral(node.leftChild);
preOrderTraveral(node.rightChild);
}
前序遍历(非递归)
用非递归的方式进行二叉树的遍历,我们可以用栈来存储二叉树的节点,然后打印输出。
思路:
先访问根节点,然后依次访问最左侧的节点,并压入栈中,如果没有左节点则进行弹栈操作,并指向弹出节点的右节点。
//非递归方式前序遍历
/*
思路:
先访问根节点,若左节点非空,用指针则顺次访问左节点,并压入栈,直到左节点为空;判断栈是否为空,若栈非空,则弹出一个节点,并用指针指向该节点的右节点,重复操作。
*/
public static void preOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
while(temp != null || !stack.isEmpty()){
//节点非空时,打印输出并入栈
while(temp != null){
System.out.print(temp.data + " ");
stack.push(temp);
temp = temp.leftChild;
}
//如果节点没有左孩子,则弹出栈顶节点,并访问节点右孩子
if(!stack.isEmpty()){
temp = stack.pop();
temp = temp.rightChild;
}
}
}
中序遍历(非递归)
思路和前序遍历一样,我们只需更改打印输出的位置即可。
//非递归中序遍历
/*
思路:
先遍历一遍最左边的节点,依次压入栈中,左节点为空时弹栈,并输出,然后将指针指向右节点。
*/
public static void inOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
while(temp != null || !stack.isEmpty()){
while(temp != null){
stack.push(temp);
temp = temp.leftChild;
}
if(!stack.isEmpty()){
temp = stack.pop();
System.out.print(temp.data + " ");
temp = temp.rightChild;
}
}
}
后序遍历(非递归)
后序遍历有点麻烦,我们需要采用逆向思维,用两个栈来进行操作,用第一个栈来压入根节点,并弹出,然后依次压入左右节点,再弹出一个节点,重复操作,每次弹出的节点放入第二个栈来进行输出,最终得到后序序列。
//非递归后序遍历
/*
思路:
后序的遍历顺序为左右根,若我们用一个栈按照根右左的顺序弹出节点,同时放入另一个栈中,那么后一个栈的弹出顺序就是正确的后序顺序
于是:我们将根节点压入栈,然后弹出根节点,再依次将左右节点压入栈中,重复操作;用另一个栈存放每次弹出的节点,然后输出后一个栈即可。
*/
public static void postOrder(TreeNode root){
Stack<TreeNode> stack1 = new Stack<TreeNode>();
Stack<TreeNode> stack2 = new Stack<TreeNode>();
TreeNode temp = root;
if(temp != null)
stack1.push(temp);
while(!stack1.isEmpty()){
temp = stack1.pop();
stack2.push(temp);
if(temp.leftChild != null)
stack1.push(temp.leftChild);
if(temp.rightChild != null)
stack1.push(temp.rightChild);
}
while(!stack2.isEmpty()){
temp = stack2.pop();
System.out.print(temp.data +" ");
}
}
层序遍历(非递归)
层序遍历是按层进行遍历,一层一层往下,由此可以使用先进先出的线性结构:队列。
思路:根节点入队列,然后出队列,并输出,同时其左节点和右节点依次入队列,重复操作。要注意的是,入队我们使用方法offer(),而不是add(),避免了队满时抛异常的情况;同理,出队我们使用方法poll(),而不是remove(),避免了队空时抛异常的情况。
//层序遍历
public static void levelOrder(TreeNode root){
//LinkedList类实现了Deque接口,而Deque继承自Queue接口,因此LinkedList可以当成Queue来用
Queue<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode temp = root;;
queue.offer(temp);//offer()方法与add()方法类似,但是当队列满时,不像add()一样抛出异常,而是返回false
while(!queue.isEmpty()){
temp = queue.poll();//poll()方法与remove()方法都是移除队首元素,但是当队列为空时,poll()返回null
System.out.print(temp.data + " ");
if(temp.leftChild != null)
queue.offer(temp.leftChild);
if(temp.rightChild != null)
queue.offer(temp.rightChild);
}
}
完整代码
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;
public class MyBinaryTree {
private static class TreeNode {
int data;
TreeNode leftChild;
TreeNode rightChild;
TreeNode(int data){
this.data = data;
}
}
//以前序方式创建二叉树
//递归实现,将每一个递归的过程写在框中,以便梳理过程
public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
TreeNode node = null;
if(inputList == null || inputList.isEmpty()){
return null;
}
Integer data = inputList.removeFirst();//返回LinkedList的第一个元素,并将头指针移动到下一个节点。
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.print(node.data + " ");
preOrderTraveral(node.leftChild);
preOrderTraveral(node.rightChild);
}
//中序遍历
public static void inOrderTraveral(TreeNode node){
if(node == null)
return;
inOrderTraveral(node.leftChild);
System.out.print(node.data + " ");
inOrderTraveral(node.rightChild);
}
//后序遍历
public static void postOrderTraveral(TreeNode node){
if(node == null)
return;
postOrderTraveral(node.leftChild);
postOrderTraveral(node.rightChild);
System.out.print(node.data + " ");
}
//非递归方式前序遍历
/*
思路:
先访问根节点,若左节点非空,用指针则顺次访问左节点,并压入栈,直到左节点为空;判断栈是否为空,若栈非空,则弹出一个节点,并用指针指向该节点的右节点,重复操作。
*/
public static void preOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
while(temp != null || !stack.isEmpty()){
//节点非空时,打印输出并入栈
while(temp != null){
System.out.print(temp.data + " ");
stack.push(temp);
temp = temp.leftChild;
}
//如果节点没有左孩子,则弹出栈顶节点,并访问节点右孩子
if(!stack.isEmpty()){
temp = stack.pop();
temp = temp.rightChild;
}
}
}
//非递归中序遍历
/*
思路:
先遍历一遍最左边的节点,依次压入栈中,左节点为空时弹栈,并输出,然后将指针指向右节点。
*/
public static void inOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
while(temp != null || !stack.isEmpty()){
while(temp != null){
stack.push(temp);
temp = temp.leftChild;
}
if(!stack.isEmpty()){
temp = stack.pop();
System.out.print(temp.data + " ");
temp = temp.rightChild;
}
}
}
//非递归后序遍历
/*
思路:
后序的遍历顺序为左右根,若我们用一个栈按照根右左的顺序弹出节点,同时放入另一个栈中,那么后一个栈的弹出顺序就是正确的后序顺序
于是:我们将根节点压入栈,然后弹出根节点,再依次将左右节点压入栈中,重复操作;用另一个栈存放每次弹出的节点,然后输出后一个栈即可。
*/
public static void postOrder(TreeNode root){
Stack<TreeNode> stack1 = new Stack<TreeNode>();
Stack<TreeNode> stack2 = new Stack<TreeNode>();
TreeNode temp = root;
if(temp != null)
stack1.push(temp);
while(!stack1.isEmpty()){
temp = stack1.pop();
stack2.push(temp);
if(temp.leftChild != null)
stack1.push(temp.leftChild);
if(temp.rightChild != null)
stack1.push(temp.rightChild);
}
while(!stack2.isEmpty()){
temp = stack2.pop();
System.out.print(temp.data +" ");
}
}
public static void main(String[] args) {
//LinkedList是一个双向链表,有指向下一节点的指针next和指向前一节点的指针prev
//泛型,LinkedList中只能存放整形变量
//Integer是int的包装类,装箱:将基本数据类型(如int)转换为包装类(如Integer),拆箱:将包装类转换为基本数据类型,有自动装箱/拆箱和手动装箱/拆箱之分。
//Arrays类提供了操纵数组的方法,如Arrays.toString(),Arrays.sort(Object[] array),Arrays.asList(整型数组),该方法将整型数组转化为List
LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]
{3,2,9,null,null,10,null,null,8,null,4}));//前序遍历序列,包括叶子的null节点
TreeNode treeNode = createBinaryTree(inputList);
System.out.println("前序遍历递归方式:");
preOrderTraveral(treeNode);
System.out.println();
System.out.println("中序遍历递归方式:");
inOrderTraveral(treeNode);
System.out.println();
System.out.println("后序遍历递归方式:");
postOrderTraveral(treeNode);
System.out.println();
System.out.println("前序遍历非递归方式:");
preOrder(treeNode);
System.out.println();
System.out.println("中序遍历非递归方式:");
inOrder(treeNode);
System.out.println();
System.out.println("后序遍历非递归方式:");
postOrder(treeNode);
System.out.println();
System.out.println("层序遍历:");
levelOrder(treeNode);
}
}