1 二叉树的先序遍历
先序遍历从树的头节点开始,按照先根节点,然后左子树,再右子树的顺序遍历整棵树。
先给出二叉树节点的定义
package binaryStree;
/**
* 二叉树节点定义
* @author 小锅巴
*/
public class TreeNode {
public TreeNode left;//左链接
public TreeNode right;//右链接
public String value;
public TreeNode(String value){
this.value = value;
}
}
文中所构造的二叉树:
递归实现就不多说了。重点说下非递归实现,个人觉得要正真理解二叉树的遍历就应该写非递归遍历,递归隐藏了遍历的过程。
方式一:充分利用栈的“先进后出”,每次弹出栈顶节点
1. 声明一个树节点变量curNode,声明一个栈s,将头节点压入s中
2. 出栈,将出栈节点赋给前节点curNode,打印节点的value
3. 若curNode有孩子,则将孩子压入s中,先右孩子,再左孩子
4. 重复2 3,直到栈为空
方式二:利用二叉树的递归定义,先不停的遍历左子树并将沿路节点进栈,然后出栈切换到右子树
1. 声明一个树节点变量curNode,curNode初始化为树的头节点,声明一个栈s,
2. 先打印节点的value,将其压入s,然后将curNode的左孩子赋给curNode,
3. 重复2,直到当前节点为null,出栈,将出栈节点赋给curNode,再将curNode的右孩子赋给curNode
4. 重复2 3,直到当前结点为null且栈为空
5.
注意这里的条件:当前节点为null且栈为空,如果只有前者,那么遍历到叶子节点时,比如遍历到了树的左边界,此时栈是不为空的,右子树还没得到遍历;如果只有后者,那么一开始就要将头节点入栈,否则无法完成遍历,可是在遍历左子树时要将当前结点压入栈,那么树的头节点就会被压入两次,从而导致头节点会被遍历两次
package binaryStree;
import java.util.Stack;
/**
* 二叉树先序遍历
* @author 小锅巴
*/
public class PreOrdreTraversal {
//递归实现
public static void traversal_1(TreeNode node){
if(node == null)
return;
System.out.print(node.value+" ");
traversal_1(node.left);
traversal_1(node.right);
}
//非递归实现方式一
public static void traversal_2(TreeNode node){
TreeNode curNode;
Stack<TreeNode> s = new Stack<>();
s.push(node);
//充分利用栈的先进后出
while(!s.isEmpty()){
curNode = s.pop();
System.out.print(curNode.value+" ");
if(curNode.right != null)
s.push(curNode.right);
if(curNode.left != null)
s.push(curNode.left);
}
}
//非递归实现方式二
public static void traversal_3(TreeNode node){
TreeNode curNode = node;
Stack<TreeNode> s = new Stack<>();
while(!(curNode == null && s.isEmpty())){
//实现遍历左子树
while(curNode != null){
System.out.print(curNode.value+" ");
s.push(curNode);
curNode = curNode.left;
}
//实现遍历右子树
curNode = s.pop();
curNode = curNode.right;
}
}
public static void main(String[] args) {
//新键一个二叉树
TreeNode root = new TreeNode("A");
TreeNode node2 = new TreeNode("B");
TreeNode node3 = new TreeNode("C");
TreeNode node4 = new TreeNode("D");
TreeNode node5 = new TreeNode("E");
TreeNode node6 = new TreeNode("F");
TreeNode node7 = new TreeNode("G");
TreeNode node8 = new TreeNode("H");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
node4.right = node7;
node7.left = node8;
System.out.println("递归实现:");
traversal_1(root);
System.out.println();
System.out.println("非递归实现方式一:");
traversal_2(root);
System.out.println();
System.out.println("非递归实现方式二:");
traversal_3(root);
System.out.println();
}
}
/**
输出
递归实现:
A B D G H E C F
非递归实现方式一:
A B D G H E C F
非递归实现方式二:
A B D G H E C F
*/
2 二叉树的中序遍历
中序遍历又称顺序遍历,先左子树,根节点,最后右子树。
非递归实现思路与先序遍历的第二种实现类似,也是先不停的遍历左子树,并将沿路节点都压入栈,直到当前节点为空,此时遍历到了树的左边界,出栈,然后打印当前节点的值,再切换到右子树
- 声明一个树节点变量curNode,curNode初始化为树的头节点,声明一个栈s,
- 将其压入s,然后将curNode的左孩子赋给curNode,
- 重复2,直到当前节点为null,出栈,将出栈节点赋给curNode,打印当前节点curNode的value,再将curNode的右孩子赋给curNode
- 重复2 3,直到当前结点为null且栈为空
package binaryStree;
import java.util.Stack;
/**
* 二叉树中序遍历
* @author 小锅巴
*/
public class InOrderTraversal {
public static void traversal_1(TreeNode node){
if(node == null)
return;
traversal_1(node.left);
System.out.print(node.value+" ");
traversal_1(node.right);
}
public static void traversal_2(TreeNode node){
TreeNode curNode = node;
Stack<TreeNode> s = new Stack<TreeNode>();
while(curNode != null || !s.isEmpty()){
//不停遍历左子树,直到树的边界
while(curNode != null){
s.push(curNode);
curNode = curNode.left;
}
curNode = s.pop();
//实现先访问左孩子
System.out.print(curNode.value+" ");
//切换到右子树
curNode = curNode.right;
}
}
public static void main(String[] args) {
//新键一个二叉树
TreeNode root = new TreeNode("A");
TreeNode node2 = new TreeNode("B");
TreeNode node3 = new TreeNode("C");
TreeNode node4 = new TreeNode("D");
TreeNode node5 = new TreeNode("E");
TreeNode node6 = new TreeNode("F");
TreeNode node7 = new TreeNode("G");
TreeNode node8 = new TreeNode("H");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
node4.right = node7;
node7.left = node8;
System.out.println("递归实现:");
traversal_1(root);
System.out.println();
System.out.println("非递归实现方式:");
traversal_2(root);
System.out.println();
}
}
/**
输出
递归实现:
D H G B E A F C
非递归实现方式:
D H G B E A F C
*/
3 二叉树的后序遍历
后序遍历先左子树,右子树,最后根节点。如果是用一个栈来实现,相比先序遍历和中序遍历,非递归实现要麻烦些,因为是先左右子树,再根节点,所以一个根节点实际上是访问了两次,而先序和中序是一次,即弹出栈顶元素即可实现回到父节点,但是后序不可以,访问左子树返回到父节点若弹出栈顶节点,那么访问右子树就回不到父节点。
方式一:使用两个栈,一个实现后续遍历,另一个存储沿路遍历的节点,注意每个节点都要压入弹出这两个栈的,故压入顺序和一个栈不同
1. 声明两个栈s1 s2,并将树的头节点压入s1,声明树节点curNode表示当前节点,
2. s1出栈,出栈元素赋给curNode,并将curNode压入s2,若当前节点curNode有孩子,则将孩子压入s1,先左孩子,再右孩子
3. 重复2直到栈s1为空
4. 依次将s2的节点出栈并打印
方式二:使用一个栈,对比中序遍历,打印当前节点的条件是当前节点为叶子节点或者其子树得到了遍历,这时就可以弹出栈顶元素,否则不弹出且将当前节点的孩子压入栈
1. 声明一个栈s,并将树的头节点压入s,声明两个树节点curNode和preNode,preNode初始化为null,preNode表示以该节点为根的整棵树遍历完了
2. 获得s的栈顶节点,赋给curNode,不出栈,若节点curNode的子树都被遍历了(即curNode的孩子为preNode)或者该节点为叶子节点,则可打印其值,然后弹出栈顶节点, 最后将preNode更新为当前节点Node,
3. 接2,否则不打印、不出栈、不更新preNode,而是将curNode的孩子入栈,先右孩子,再左孩子
4. 重复2 3,直到栈为空
package binaryStree;
import java.util.Stack;
/**
* 后序遍历
* @author 小锅巴
*/
public class PostOrderTraversal {
//递归遍历
public static void traversal_1(TreeNode node){
if(node == null)
return;
traversal_1(node.left);
traversal_1(node.right);
System.out.print(node.value+" ");
}
//使用两个栈
public static void traversal_2(TreeNode node){
TreeNode curNode;
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
s1.push(node);
//实现遍历
while(!s1.isEmpty()){
curNode = s1.pop();
s2.push(curNode);//s2存储沿路的节点
if(curNode.left != null)
s1.push(curNode.left);
if(curNode.right != null)
s1.push(curNode.right);
}
//实现打印
while(!s2.isEmpty())
System.out.print(s2.pop().value+" ");
}
//使用一个栈
public static void traversal_3(TreeNode node){
TreeNode curNode;
TreeNode preNode = null;
Stack<TreeNode> s = new Stack<>();
s.push(node);
while(!s.isEmpty()){
curNode = s.peek();
if(
(curNode.left == null && curNode.right == null)//此时curNode为叶子节点
|| (preNode != null && (curNode.left == preNode || curNode.right == preNode))
//curNode的子树得到了遍历,至少有一个子树,且子树不能为空,不然就可能是叶子节点
)
{
System.out.print(curNode.value+" ");
preNode = curNode;//标记此节点为根的整棵树都遍历完了
s.pop();//遍历完了就可以出栈
}else{
if(curNode.right != null)
s.push(curNode.right);
if(curNode.left != null)
s.push(curNode.left);
}
}
}
public static void main(String[] args) {
//新键一个二叉树
TreeNode root = new TreeNode("A");
TreeNode node2 = new TreeNode("B");
TreeNode node3 = new TreeNode("C");
TreeNode node4 = new TreeNode("D");
TreeNode node5 = new TreeNode("E");
TreeNode node6 = new TreeNode("F");
TreeNode node7 = new TreeNode("G");
TreeNode node8 = new TreeNode("H");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
node4.right = node7;
node7.left = node8;
System.out.println("递归实现:");
traversal_1(root);
System.out.println();
System.out.println("非递归实现方式一:");
traversal_2(root);
System.out.println();
System.out.println("非递归实现方式二:");
traversal_3(root);
System.out.println();
}
}
/**
输出:
递归实现:
H G D E B F C A
非递归实现方式一:
H G D E B F C A
非递归实现方式二:
H G D E B F C A
*/
其实还有一种实现的方法,也是使用两个栈,其中一个栈用来标记当前节点为根节点的树是否遍历完,不如一个栈和两个TreeNode变量的实现高效,感兴趣的话可以参考这里。
4 二叉树按层遍历
4.1不给出层信息
即遍历结果是一行,不是多行。遍历按层从上到下,每层从左到右。
- 声明一个队列q,声明一个树节点curNode,初始时将树的头节点压入队列q
- 出队列,将出队列的节点赋给curNode,打印当前节点curNode的value,并将curNode的孩子压入队列,先左孩子,再右孩子
- 重复2,直到队列为空
代码后面一并给出
4.2给出层信息
即没层打印完要换行。还是从上到下,从左到右
前面的思路可以实现按层遍历了,现在的问题是如何换行,使用两个TreeNode变量last表示当前行的最后一个节点,nlast表示下一行的最后一个节点,当当前节点curNode为last时就换行,并将last更新为nlast,那么nlast的更新如何实现呢?跟踪每次压入队列q的节点,考虑curNode为last时,此时需要把curNode的孩子压入队列,所以可实现nlast的更新。可以得出换行要放在curNode孩子压入队列的后面。
package binaryStree;
import java.util.*;
/**
* 二叉树层序遍历
* @author 小锅巴
*/
public class LayerTraversal {
//不给出行信息
public static void traversal_1(TreeNode node){
Queue<TreeNode> q = new LinkedList<>();
q.add(node);
TreeNode curNode;
while(!q.isEmpty()){
curNode = q.poll();
System.out.print(curNode.value+" ");
if(curNode.left != null)
q.add(curNode.left);
if(curNode.right != null)
q.add(curNode.right);
}
}
//给出行信息
public static void traversal_2(TreeNode node){
Queue<TreeNode> q = new LinkedList<>();
q.add(node);
TreeNode curNode;
TreeNode last = node;//方便头节点的换行
TreeNode nlast = null;
while(!q.isEmpty()){
curNode = q.poll();
System.out.print(curNode.value+" ");
if(curNode.left != null){
nlast = curNode.left;//和下面的nlast赋值语句一起构成nlast的更新
q.add(curNode.left);
}
if(curNode.right != null){
nlast = curNode.right;
q.add(curNode.right);
}
//换行并更新last
if(curNode == last){
System.out.println();
last = nlast;
}
}
}
public static void main(String[] args) {
//新建一个二叉树
TreeNode root = new TreeNode("A");
TreeNode node2 = new TreeNode("B");
TreeNode node3 = new TreeNode("C");
TreeNode node4 = new TreeNode("D");
TreeNode node5 = new TreeNode("E");
TreeNode node6 = new TreeNode("F");
TreeNode node7 = new TreeNode("G");
TreeNode node8 = new TreeNode("H");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
node4.right = node7;
node7.left = node8;
System.out.println("按层遍历,但是不给出行信息:");
traversal_1(root);
System.out.println();
System.out.println("按层遍历,给出行信息:");
traversal_2(root);
System.out.println();
}
}
/**
输出:
按层遍历,但是不给出行号信息:
A B C D E F G H
按层遍历,给出行号信息:
A
B C
D E F
G
H
*/
5 二叉树遍历推断
这道题来自2016年4月阿里巴巴实习生招聘的一个小题:
已知一颗二叉树的先序遍历为:A B C D E F G H I J,中序遍历为:C B A E F D I H J G,那么其后序遍历为?
如果真正理解了二叉树的遍历,是很好做的,很容易把这颗二叉树重建出来,比较基础。
1. 从先序遍历知A为树的头节点,再从中序遍历知C B是在A的左子树中,E F D I H J G在A的右子树中
2. 从先序遍历B C和中序遍历C B,知B为A的左孩子,C为B的左孩子
3. 从先序D E F G H I J和中序E F D I H J G知D为A的右孩子,E F为D的左子树,G H I J为D的右子树
4. 由先序E F和中序E F,知E为D的左孩子,F为E的右孩子
5. 由先序G H I J和中序I H J G,知G为D的右孩子,H为G的左孩子,I为G的左孩子,J为G的右孩子
根据以上推导(有点分治的味道),重建二叉树如下:
所以后序遍历为C B F E I J H G D A
附上完整代码:
package binaryTree;
import java.util.Stack;
/**
* 一个二叉树的测试
* @author 小锅巴
*/
class TreeNode<V> {
public TreeNode left;
public TreeNode right;
public V value;
public TreeNode(V value){
this.value = value;
}
}
public class Test {
public static void preOrderTraversal(TreeNode node){
Stack<TreeNode> s = new Stack<>();
TreeNode curNode;
s.push(node);
while(!s.isEmpty()){
curNode = s.pop();
System.out.print(curNode.value+" ");
if(curNode.right != null)
s.push(curNode.right);
if(curNode.left != null)
s.push(curNode.left);
}
}
public static void inOrderTraversal(TreeNode node){
Stack<TreeNode> s = new Stack<>();
TreeNode curNode = node;
// s.push(node);
while(!(curNode == null && s.isEmpty())){
while(curNode != null){
s.push(curNode);
curNode = curNode.left;
}
if(!s.isEmpty()){
curNode = s.pop();
System.out.print(curNode.value+" ");
curNode = curNode.right;
}
}
}
public static void postOrderTraversal(TreeNode node){
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
TreeNode curNode;
s1.push(node);
while(!s1.isEmpty()){
curNode = s1.pop();
s2.push(curNode);
if(curNode.left != null)
s1.push(curNode.left);
if(curNode.right != null)
s1.push(curNode.right);
}
while(!s2.isEmpty())
System.out.print(s2.pop().value+" ");
}
public static void main(String[] args) {
//根据推导结果重建二叉树
TreeNode<String> root = new TreeNode<>("A");
TreeNode<String> node2 = new TreeNode<>("B");
TreeNode<String> node3 = new TreeNode<>("C");
TreeNode<String> node4 = new TreeNode<>("D");
TreeNode<String> node5 = new TreeNode<>("E");
TreeNode<String> node6 = new TreeNode<>("F");
TreeNode<String> node7 = new TreeNode<>("G");
TreeNode<String> node8 = new TreeNode<>("H");
TreeNode<String> node9 = new TreeNode<>("I");
TreeNode<String> node10 = new TreeNode<>("J");
root.left = node2;
root.right = node4;
node2.left = node3;
node4.left = node5;
node5.right = node6;
node4.right = node7;
node7.left = node8;
node8.left = node9;
node8.right = node10;
System.out.print("先序遍历:");
preOrderTraversal(root);
System.out.println();
System.out.print("中序遍历:");
inOrderTraversal(root);
System.out.println();
System.out.print("后序遍历:");
postOrderTraversal(root);
System.out.println();
System.out.println("按层遍历:");
LayerTraversal.traversal_2(root);
System.out.println();
}
}
/**
输出:
先序遍历:A B C D E F G H I J
中序遍历:C B A E F D I H J G
后序遍历:C B F E I J H G D A
按层遍历:
A
B D
C E G
F H
I J
*/