二叉树遍历(前序,中序,后序,层序)
本文主要讲前中后序的递归和非递归方法以及层序遍历方法。
所有代码使用Java编写
首先给出二叉树节点类
static class ListNode {
int val;
ListNode left;
ListNode right;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
}
其中测试的二叉树的结构和应该得到的结果如下
// 1
// / \
// 2 3
// / \ \
// 4 5 6
// / \
// 7 8
// preOrderTraverse 前序 1 2 4 5 7 8 3 6
// medOrderTraverse 中序 4 2 7 5 8 1 3 6
// postOrderTraverse 后序 4 7 8 5 2 6 3 1
三种顺序的遍历方法
先序:考察到一个节点后,即刻输出该节点的值,并继续遍历其左右子树。(根左右)
中序:考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右)
后序:考察到一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值。(左右根)
先序遍历
递归方法
递归的方法比较好理解,先打印当前节点的值,然后再遍历左右子树,中序和后序的递归方法同以下方法是类似的。
// preOrderTraverse 前序 1 2 4 5 7 8 3 6
//前序遍历
//递归
public static void preOrderTraverse1(ListNode root) {
if (root != null) {
System.out.print(root.val + " ");
preOrderTraverse1(root.left);
preOrderTraverse1(root.right);
}
}
非递归方法
遍历一个树需要使用栈来存储节点,这里使用LinkedList来作为栈,其中一些方法需要仔细理解
- push()方法:将节点压入栈顶,等同于addFisrt()方法,仔细看代码的话会发现push()方法调用的就是addFirst()方法。
- addLast()方法:将节点压入栈尾。
- pop()方法:将节点从栈顶中取出(意思是将栈顶元素取出,并删除栈顶中相应的节点)。
- peek()方法:将节点从栈顶借出(意思是复制一个元素,但是不删除相应的栈顶元素)。
这里的思路是这样的,使用栈来存储每次需要入栈的根节点(左子树、右子树的根节点)。因为先序遍历是先打印当前节点再访问左右子树,故可以使用动态节点来判断当前节点的左子树情况,即当前节点左子树是否存在?直至不存在再去判断右子树。
这里有一个重要的地方是:当左子树的根节点是null时,需要继续访问右子树,这里利用栈 pop出栈顶节点,因为这个栈顶节点已经访问过并且打印出来了。
//前序遍历
//非递归
public static void preOrderTraverse2(ListNode root) {
LinkedList<ListNode> stack = new LinkedList<>();
//动态指针 用于 锁定当前节点
ListNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
//分开两段
if (pNode != null) {
//打印当前节点
System.out.print(pNode.val + " ");
//当前节点入栈
stack.push(pNode);
//更新指针节点
pNode = pNode.left;
}
//如果当前指针节点为空,从 栈中 出一个节点 继续作为指针节点
else {
ListNode node = stack.pop();
pNode = node.right;
}
}
}
中序遍历
// medOrderTraverse 中序 4 2 7 5 8 1 3 6
递归方法
//中序遍历
//递归
public static void medOrderTransverse1(ListNode root) {
if (root != null) {
medOrderTransverse1(root.left);
System.out.print(root.val + " ");
medOrderTransverse1(root.right);
}
}
非递归遍历
这里的思路参考前序遍历,这里转换为左中右,故当左子树为null的时候,我们利用pop方法从栈中提取栈顶节点(因为左子树为空,故栈顶节点为左中右的中且存在,故可以打印,在继续判断栈顶节点的右子树是否存在即可)
//中序遍历
//非递归
public static void medOrderTransverse2(ListNode root) {
LinkedList<ListNode> stack = new LinkedList<>();
ListNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
//将当前节点加入 栈中
stack.push(pNode);
//将动态指针重新指向当前节点的 左节点
pNode = pNode.left;
} else {
ListNode node = stack.pop();
//打印当前节点
System.out.print(node.val + " ");
pNode = node.right;
}
}
}
后序遍历
- 后序遍历不同于上述两种方法,后序遍历需要访问完左右子树后才可以访问当前节点,那么如何确定左右子树都访问完了呢?需要设置一个节点来确定,这里设置lastVisit。
- 判断方法为,若lastVisit等于当前考察节点的右子树,就表明当前节点的左右子树已经遍历完成,那么就可以输出当前节点。
- 同时将lastVisit节点设置为当前节点,将动态指针节点设置为null,下一轮继续访问栈顶元素即可
- 如果lastVisit节点不等于当前节点的右子树,那么据需要继续判断右子树,pNode = pNode.right
- 这里使用peek()方法查看栈顶元素,而不是取出栈顶元素,方便确定栈顶元素的右子树是否为null,不为null则栈顶元素不需要取出,为null则取出
// postOrderTraverse 后序 4 7 8 5 2 6 3 1
递归方法
//后序遍历
//递归
public static void postOrderTraverse1(ListNode root) {
if (root != null) {
postOrderTraverse1(root.left);
postOrderTraverse1(root.right);
System.out.print(root.val + " ");
}
}
非递归方法
public static void post(ListNode root) {
LinkedList<ListNode> stack = new LinkedList<>();
ListNode pNode = root;
ListNode lastVisit = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
stack.push(pNode);
pNode = pNode.left;
} else {
ListNode node = stack.peek();
ListNode right = node.right;
if (right == null || right == lastVisit) {
System.out.print(node.val + " ");
stack.pop();
lastVisit =node;
pNode = null;
}
else {
pNode = node.right;
}
}
}
}
层序遍历
这里使用的是addLast()方法,方便加入到栈尾,这样每次出栈的点都是从上层开始的
//层序遍历
public static void levelTraverse(ListNode root) {
if (root == null)
return;
//用于存储节点
LinkedList<ListNode> stack = new LinkedList<>();
//存储根节点
stack.push(root);
while (!stack.isEmpty()) {
ListNode node = stack.pop();
System.out.print(node.val + " ");
if (node.left != null)
stack.addLast(node.left);
if (node.right != null)
stack.addLast(node.right);
}
}