二叉树遍历常用递归:
先画一个二叉树举例:
代码中定义二叉树:
//定义节点类
static class Node {
int val; //数据域
public Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
public Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
public Node(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
static Node build(){
Node A = new Node('A');
Node B = new Node('B');
Node C = new Node('C');
Node D = new Node('D');
Node E = new Node('E');
Node F = new Node('F');
Node G = new Node('G');
A.left = B;
B.left = D;
B.right = E;
E.left = G;
A.right = C;
C.right = F;
return A;
}
先序遍历
递归
NLR(先根再左后右)
先序遍历就是先从根节点进去之后先访问根节点,之后再遍历根节点的左子树的根节点之后再先左子树再右子树依次进行直到将总的根节点的左子树遍历完,之后再遍历根节点的右子树。总的来说就是先根节点之后能左先左,左边结束开始右边。
先序遍历A->B->D->E->G->C->F
按照上图红色表示向下一步,蓝色表示访问完毕返回。
1、先从根节点A开始访问;A
2、从左子树开始到第二步B点;A->B
3、访问完B点后再访问B的左子树,这就进行到第三步访问到D节点;A->B->D
4、之后D的左右子树都为空后D节点遍历完毕,向上一级返回,就是D节点蓝色的箭头所指的;A->B->D
5、当B节点收到D的返回后代表B的左子树已经遍历完了;
6、之后再开始遍历B的右子树,到第四步E,访问E节点;A->B->D->E
7、E节点访问完之后再遍历E的左子树,到第五步访问节点G;A->B->D->E->G
8、访问完G节点后发现G节点左右子树都为空,G节点遍历完毕,向上一级E节点返回,即蓝色箭头所指;
9、此时E的左子树遍历完成,E的右子树为空,E节点遍历完毕,向上一级返回;
10、B节点收到返回意味着B的右子树遍历完毕,此时B点的遍历完成,向A节点返回;
11、A节点收到返回意味着A节点的左子树遍历完毕;
12、此时开始A节点的右子树遍历,第六步到C节点访问C节点;A->B->D->E->G->C
13、之后发现C节点左子树为空,则访问C的右子树F;A->B->D->E->G->C->F
14、发现F节点的左右子树都为空后,F节点遍历完毕,向上一级返回,蓝色箭头指向C;
15、C收到返回意味着C节点右子树遍历完毕,向上返回到A;
16、A收到返回意味着A的右子树全部访问完毕,二叉树遍历完成。 A->B->D->E->G->C->F
public static void preOrder(Node root){
//先访问根节点,先递归访问左子树再递归访问右子树
//如果是空树,不进行操作
if(root == null){
return;
}
System.out.print(root.val+" ");
//访问根的左节点
preOrder(root.left);
//访问根的右节点
preOrder(root.right);
}
迭代
1、首先创建一个栈,初始情况下,把根节点入栈
2、进入循环
a)取栈顶元素(出栈)
b)访问该元素
c)如果该元素的右子树不为空,就入栈;如果左子树不为空也入栈,当栈为空时,遍历完成。
先判断右子树不为空,再判断左子树,如果不为空则入栈,重复循环中ab两个操作直到栈为空。
public static void preOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
Node top = stack.pop();
System.out.print(top.val+" ");
if(top.right != null){
stack.push(top.right);
}
if(top.left != null){
stack.push(top.left);
}
}
}
中序遍历
递归
LNR(先左再根后右)
中序遍历就是先从根节点进去之后不访问根节点先访问根节点的左子树,之后再遍历左子树根节点的左子树之后再访问到左子树的根节点,然后遍历左子树的右子树,先访问右子树的左子树,之后再是根节点,最后是右子树的右子树。总的来说就是能左先左,左边结束,访问根节点然后再开始右边。
中序遍历== D->B->G->E->A->C->F==
按照上图红色表示向下一步,蓝色表示访问完毕返回。
1、从根节点进入后先不访问根节点A,先访问根节点的左子树B,进入B后判断是否有左子树,如有则不访问左子树的根节点,进入到左子树的左子树D,之后D的左子树为空,之后访问D,D右子树为空则向上返回。== D==
2、B节点收到返回此时意味着B的左子树已经遍历完毕,此时访问B。D->B
3、B节点访问完毕后,开始遍历B节点的右子树,进入到E判断是否有左子树,有则进入到E的左子树G,此时再判断G是否有左子树,G无左子树则访问G点,之后G的右子树为空则向上返回。D->B->G
4、E节点收到返回此时意味着E的左子树已经遍历完毕,此时访问E。D->B->G->E
5、E节点访问完毕后,开始遍历E节点的右子树,发现E的右子树为空,则向上返回到B。
6、B收到返回后,B节点的右子树遍历完成,B节点中序遍历完成向上返回,A收到返回后意味着A的左子树已经遍历完毕,此时访问A的根节点。D->B->G->E->A
7、A的根节点访问完毕后,开始遍历A的右子树,此时进入到C节点,发现C节点的左子树为空,则访问C的根节点。D->B->G->E->A->C
8、C节点访问完毕后进入到C的右子树F节点,然后发现F节点的左子树为空,则访问F的根节点。D->B->G->E->A->C->F
9、F节点访问完毕后,开始遍历F节点的右子树,发现F的右子树为空,则向上返回到C,C收到返回后,C节点的右子树遍历完成,C节点中序遍历完成向上返回,A收到返回后意味着A的右子树已经遍历完毕,整个中序遍历完成。
public static void inOrder(Node root){
//先递归访问左子树,再访问根节点,再递归访问右子树
//如果是空树,不进行操作
if(root == null){
return;
}
//访问根的左节点
inOrder(root.left);
//访问根节点
System.out.print(root.val+" ");
//访问根的右节点
inOrder(root.right);
}
迭代
1、需要创建一个栈,设定一个cur引用从根节点出发
2、只要cur不为空就把cur入栈,同时cur向左移直到cur为空。
3、当cur为空,(此时栈顶元素是目前的最左侧元素),出栈并访问
4、让cur指向刚刚被访问的节点的右子树,循环2、3、4步骤
//迭代
public static void inOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
while (true){
while (cur != null){
//1、cur一直往左走循环入栈直到cur为空
stack.push(cur);
cur = cur.left;
}
//2、取栈顶元素访问,如果空栈访问结束
if(stack.empty()){
break;
}
Node top = stack.pop();
System.out.print(top.val+" ");
//3、cur从top右子树出发重复之前步骤
cur = top.right;
}
}
后序遍历
递归
LRN(先左再右后根)
后序遍历就是先从根节点进去之后不访问根节点先访问根节点的左子树,之后再遍历左子树根节点的左子树之后再访问到左子树的右子树,然后遍历左子树的右子树,先访问右子树的左子树,之后是右子树的右子树,最后再是根节点。总的来说就是能左先左,左边结束开始右边最后再访问根节点。
后序遍历== D->G->E->B->F->C->A==
按照上图红色表示向下一步,蓝色表示访问完毕返回。
1、从根节点进入后先不访问根节点A,先访问根节点的左子树B,进入B后判断是否有左子树,如有则不访问左子树的根节点,进入到左子树的左子树D,之后D的左子树为空,之后访问D的右子树,D的右子树为空则访问D,向上返回。D
2、B节点收到返回此时意味着B的左子树已经遍历完毕,此时访问B的右子树。
3、遍历B节点的右子树,进入到E判断是否有左子树,有则进入到E的左子树G,此时再判断G是否有左子树,G无左子树则访问G的右子树,右子树为空,访问G点根节点,之后则向上返回。D->G
4、E节点收到返回此时意味着E的左子树已经遍历完毕,此时访问E的右子树。
5、开始遍历E节点的右子树,发现E的右子树为空,开始访问E的根节点,访问完毕后,则向上返回到B。D->G->E
6、B收到返回后,B节点的右子树遍历完成,此时再访问B的根节。B节点后序遍历完成向上返回,A收到返回后意味着A的左子树已经遍历完毕,此时访问A的右子树。D->G->E->B
7、开始遍历A的右子树,此时进入到C节点,发现C节点的左子树为空,则访问C的右子树F。
8、然后发现F节点的左子树为空,则访问F的右子树,右子树也为空,则访问F的根节点。D->G->E->B->F
9、F的根节点访问完毕后,则向上返回到C,C收到返回后,C节点的右子树遍历完成,访问C的根节点。D->G->E->B->F->C
10、C节点后序遍历完成向上返回,A收到返回后意味着A的右子树已经遍历完毕,然后访问A的根节点,之后整个后序遍历完成。D->G->E->B->F->C->A
public static void posOrder(Node root){
//先递归访问左子树,再递归访问右子树,最后访问根节点
//如果是空树,不进行操作
if(root == null){
return;
}
//访问根的左节点
posOrder(root.left);
//访问根的右节点
posOrder(root.right);
//访问根节点
System.out.print(root.val+" ");
}
迭代
1、需要创建一个栈,设定一个cur引用从根节点出发
2、只要cur不为空就把cur入栈,同时cur向左移直到cur为空。
3、当cur为空,取栈顶元素(如果栈顶元素被访问即被打印才可出栈,否则依然在栈里面),判断是否能够访问:
a)可以访问:
如果栈顶元素右子树为null,则可以访问
栈顶元素已经被访问过了,则可以访问,定义一个prev变量记录上一个被访问过的元素
b)不能访问:
让cur从栈顶元素的右子树出发继续进行1、2、3步骤
//迭代
public static void posOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
Node prev = null;//用来记录上个被访问过的节点
while (true){
//1、cur一直往左走循环入栈直到cur为空
while (cur != null){
stack.push(cur);
cur = cur.left;
}
//2、取栈顶元素,判断是否能访问
if(stack.empty()){
//遍历结束
break;
}
Node top = stack.peek();//取栈顶元素进行判断并没有出栈,只有节点被访问过才会出栈
//满足下面两个条件可以访问
//a)如果栈顶元素右子树为null,则可以访问
//b)栈顶元素已经被访问过了,则可以访问
if(top.right == null || top.right == prev){
//可以访问top
System.out.print(top.val+" ");
stack.pop();//出栈
prev = top;//将上一个被访问过的元素放入prev
}else {
//3、cur从top右子树出发重复之前步骤
cur = top.right;
}
}
}
层序遍历
顾名思义一层一层进行遍历,每一层从左到右依次遍历。
A->B->C->D->E->F->G
层序遍历比较简单,不需要使用递归,但是要借用队列,队列是先进先出,每次访问到某个节点的时候依次将根节点,左子树节点,右子树节点放入到队列当中,每次出队列将出的节点的左右子树放入到队列中。
最开始的时候让根节点入队列,
之后循环执行直到队列为空:
1、取出队首元素并访问
2、把当前访问的元素的左子树和右子树分别入队列
上图所示,先把根节点A入队列,之后取出队首元素即A并访问A节点,之后将A的左右子树放入队列即B、C;接下来就是循环中,先取出此时队首元素B并访问,之后将B的左右子树放入队列中,此时队列中就为C、D、E。之后不断循环就可以将树层序遍历。
public static void leveOrder(Node root){
//如果是空树,不进行操作
if(root == null){
return;
}
//创建一个队列,初始情况把根节点加入到队列
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
//2、进入循环,当队列为空时结束循环
while (!queue.isEmpty()){
//取出队首元素
Node cur = queue.poll();
//访问当前元素
System.out.print(cur.val+" ");
//把当前节点左右子树放入队列
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
最后四种遍历代码:
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Tree {
//定义节点类
static class Node {
char val; //数据域
public Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
public Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
public Node(char val) {
this.val = val;
this.left = null;
this.right = null;
}
}
static Node build(){
Node A = new Node('A');
Node B = new Node('B');
Node C = new Node('C');
Node D = new Node('D');
Node E = new Node('E');
Node F = new Node('F');
Node G = new Node('G');
A.left = B;
B.left = D;
B.right = E;
E.left = G;
A.right = C;
C.right = F;
return A;
}
//递归
public static void preOrder(Node root){
//先访问根节点,先递归访问左子树再递归访问右子树
//如果是空树,不进行操作
if(root == null){
return;
}
System.out.print(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
//迭代
public static void preOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
Node top = stack.pop();
System.out.print(top.val+" ");
if(top.right != null){
stack.push(top.right);
}
if(top.left != null){
stack.push(top.left);
}
}
}
//递归
public static void inOrder(Node root){
//先递归访问左子树,再访问根节点,再递归访问右子树
//如果是空树,不进行操作
if(root == null){
return;
}
//访问根的左节点
inOrder(root.left);
//访问根节点
System.out.print(root.val+" ");
//访问根的右节点
inOrder(root.right);
}
//迭代
public static void inOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
while (true){
while (cur != null){
//1、cur一直往左走循环入栈直到cur为空
stack.push(cur);
cur = cur.left;
}
//2、取栈顶元素访问,如果空栈访问结束
if(stack.empty()){
break;
}
Node top = stack.pop();
System.out.print(top.val+" ");
//3、cur从top右子树出发重复之前步骤
cur = top.right;
}
}
//递归
public static void posOrder(Node root){
//先递归访问左子树,再递归访问右子树,最后访问根节点
//如果是空树,不进行操作
if(root == null){
return;
}
//访问根的左节点
posOrder(root.left);
//访问根的右节点
posOrder(root.right);
//访问根节点
System.out.print(root.val+" ");
}
//迭代
public static void posOrder1(Node root){
if(root == null){
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
Node prev = null;//用来记录上个被访问过的节点
while (true){
//1、cur一直往左走循环入栈直到cur为空
while (cur != null){
stack.push(cur);
cur = cur.left;
}
//2、取栈顶元素,判断是否能访问
if(stack.empty()){
//遍历结束
break;
}
Node top = stack.peek();//取栈顶元素进行判断并没有出栈,只有节点被访问过才会出栈
//满足下面两个条件可以访问
//a)如果栈顶元素右子树为null,则可以访问
//b)栈顶元素已经被访问过了,则可以访问
if(top.right == null || top.right == prev){
//可以访问top
System.out.print(top.val+" ");
stack.pop();//出栈
prev = top;//将上一个被访问过的元素放入prev
}else {
//3、cur从top右子树出发重复之前步骤
cur = top.right;
}
}
}
public static void leveOrder(Node root){
//如果是空树,不进行操作
if(root == null){
return;
}
//创建一个队列,初始情况把根节点加入到队列
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
//2、进入循环,当队列为空时结束循环
while (!queue.isEmpty()){
//取出队首元素
Node cur = queue.poll();
//访问当前元素
System.out.print(cur.val+" ");
//把当前节点左右子树放入队列
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
public static void main(String[] args) {
Node root = build();
System.out.println("先序遍历:");
System.out.print("递归:");
preOrder(root);
System.out.print("迭代:");
preOrder1(root);
System.out.println();
System.out.println("中序遍历:");
System.out.print("递归:");
inOrder(root);
System.out.print("迭代:");
inOrder1(root);
System.out.println();
System.out.println("后序遍历:");
System.out.print("递归:");
posOrder(root);
System.out.print("迭代:");
posOrder1(root);
System.out.println();
System.out.println("层序遍历:");
leveOrder(root);
}
}
结果:
先序遍历:
递归:A B D E G C F 迭代:A B D E G C F
中序遍历:
递归:D B G E A C F 迭代:D B G E A C F
后序遍历:
递归:D G E B F C A 迭代:D G E B F C A
层序遍历:
A B C D E F G