树的先序、中序、后序、层次遍历

本文详细讲解了二叉树的层次遍历算法,通过非递归方式演示如何使用队列实现,包括先序、中序和后序遍历,并以实例演示了层次遍历在特定场景的应用,如锯齿形层次遍历。

二叉树的前序、中序、后序遍历我想大家应该都很熟悉了,那我们今天就来讲一下二叉树的层次遍历。

二叉树的前序、中序、后序遍历需要用到栈(递归的过程也就是一个栈),层次遍历需要借助队列这个数据结构。

深度优先遍历(递归版)

package com.lc.javabase;

/**
 * @author liuchao02
 * @ClassName: MyTree
 * @Description: 树 demo
 * @date 2021/2/22 17:02
 */

public class MyTree {
    int data;
    MyTree leftNode;
    MyTree rightNode;

    public MyTree(int data) {
        this.data = data;
        leftNode = null;
        rightNode = null;
    }

    public static void main(String[] args) {

    }

    private void add(MyTree myTree, int data) {
        if (myTree.data > data) {
            if (myTree.leftNode == null) {
                myTree.leftNode = new MyTree(data);
            } else {
                add(myTree.leftNode, data);
            }
        } else {
            if (myTree.rightNode == null) {
                myTree.rightNode = new MyTree(data);
            } else {
                add(myTree.rightNode, data);
            }
        }
    }

    //先序遍历
    public static void first(MyTree myTree) {
        if (myTree != null) {
            System.out.println(myTree.data + " ");
            first(myTree.leftNode);
            first(myTree.rightNode);
        }
    }

    //中序遍历
    public static void mid(MyTree myTree) {
        if (myTree != null) {
            first(myTree.leftNode);
            System.out.println(myTree.data + " ");
            first(myTree.rightNode);
        }
    }

    //中序遍历
    public static void back(MyTree myTree) {
        if (myTree != null) {
            first(myTree.leftNode);
            first(myTree.rightNode);
            System.out.println(myTree.data + " ");
        }
    }

}

深度优先遍历(非递归版,利用Stack数据结构实现)

二叉树前序遍历的非递归实现

        实现思路,先序遍历是要先访问根节点,然后再去访问左子树以及右子树,这明显是递归定义。但这里是用来实现的首先需要先从栈顶取出节点,然后访问该节点,如果该节点不为空,则访问该节点,同时把该节点的右子树先入栈,然后左子树入栈。循环结束的条件是栈中不在有节点。即 !s.empty()

public void preOrder(Node root) {
	Stack<Node> s = new Stack<Node>();
	s.push(root);
	Node p = null;
	while (!s.empty()) {
		p = s.pop();
		if (p != null) {
			System.out.print(p.val+" ");
			s.push(p.right);
			s.push(p.left);
		}
	}
}

二叉树的中序遍历非递归实现

        实现思路,中序遍历是要先遍历左子树,然后根节点,最后遍历右子树。所以需要先把根节点入栈然后在一直把左子树入栈直到左子树为空,此时停止入栈。栈顶节点就是我们需要访问的节点,取栈顶节点p并访问。然后改节点可能有右子树,所以访问完节点p后还要判断p的右子树是否为空,如果为空则接下来要访问的节点在栈顶,所以将p赋值为null。如果不为空则将p赋值为其右子树的值。 循环结束的条件是p不为空或者栈不为空。

public void inOrder(Node root) {
      Stack<Node> s = new Stack<Node>();
      Node p = root;
      do {
          while (p != null) {
             s.push(p);
             p = p.left;
          }
          p = s.pop();
          System.out.print(p.val+" ");
          if (p.right != null) {
              p = p.right;
          }
          else p = null;
     } while(p != null || !s.empty());
}

二叉树的后序遍历里非递归实现

        实现思路,在进行后序遍历的时候是先要遍历左子树,然后在遍历右子树,最后才遍历根节点。所以在非递归的实现中要先把根节点入栈 然后再把左子树入栈直到左子树为空,此时停止入栈。此时栈顶就是需要访问的元素,所以直接取出访问p。在访问结束后,还要判断被访问的节点p是否为栈顶节点的左子树,如果是的话那么还需要访问栈顶节点的右子树,所以将栈顶节点的右子树取出赋值给p。如果不是的话则说明栈顶节点的右子树已经访问完了,那么现在可以访问栈顶节点了,所以此时将p赋值为null。判断结束的条件是p不为空或者栈不为空,若果两个条件都不满足的话,说明所有节点都已经访问完成。

       public void postOrder(Node root) {
		
		Stack<Node> s = new Stack<Node>();
		Node p = root;
		while (p != null || !s.empty()) {
			while(p != null) {
				s.push(p);
				p = p.left;
			}
			p = s.pop();
			System.out.print(p.val+" ");
			//这里需要判断一下,当前p是否为栈顶的左子树,如果是的话那么还需要先访问右子树才能访问根节点
			//如果已经是不是左子树的话,那么说明左右子书都已经访问完毕,可以访问根节点了,所以讲p复制为NULL
			//取根节点
			if (!s.empty() && p == s.peek().left) {
				p = s.peek().right;
			}
			else p = null;
		}
	}

最后附上Node节点的Java类代码示例

//树的Node节点类
class Node {
	public int val; //节点值
	public Node left; //左子树
	public Node right; //右子树
	public Node() {}
	public Node(int val, Node left, Node right) {
		this.val = val;
		this.left = left;
		this.right = right;
	}
}

层次遍历的思路

我们给出一个二叉树:

这棵二叉树的层次遍历次序为:A、B、C、D、F、G

以人的思维来看层次遍历貌似比前、中、后序遍历更加简单易懂,但是程序到底如何实现这样的效果呢?这里我们需要用到一个数据结构,那就是队列

核心思想:每次出队一个元素,就将该元素的孩子节点加入队列中,直至队列中元素个数为0时,出队的顺序就是该二叉树的层次遍历结果。

可能这句话不太好理解,我们依旧是以画图的形式来表达:

1. 初始状态下,队列中只保留根节点的元素:

2. 当A出队时,将A的孩子节点加入队列中:

3.重复上面的动作,队首元素出队时,将孩子节点加入队尾.......

...........

相信看了上面的过程,大家对层次遍历的思路已经有了清晰的认识,那么我们就需要用代码来实现它了:

层次遍历的实现

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class MyTree {

    public static class TreeNode {
        public Object key;
        public TreeNode parent;
        public List<TreeNode> childrens = new ArrayList<>();

        public TreeNode(Object key) {
            this.key = key;
        }
    }

    private int size = 0;
    private TreeNode root;

    public MyTree(TreeNode root) {
        this.root = root;
        size++;
    }

    /**
     * 层次遍历
     *
     * @param treeNode
     * @return
     */
    public List<TreeNode> levelOrder(TreeNode treeNode) {
    /**
    * 层次遍历使用到了广度优先搜索,技巧:深度优先用递归,广度优先用队列。
    */
        Queue<TreeNode> queue = new LinkedList<>();
        List<TreeNode> list = new LinkedList<>();
        queue.add(treeNode);
        while (queue.size() > 0) {
        //出一个,进n个
        //出一个
            TreeNode node = queue.poll();
            list.add(node);
        //进n个
            List<TreeNode> childens = node.childrens;
            for (TreeNode childNode : childens) {
                queue.add(childNode);
            }
        }
        return list;
    }
}

注:代码中实现的是一个普通的树,不是一颗二叉树。

或者实现代码如下

package tree.层次遍历二叉树;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

class Solution {

    public static void main(String[] args) {
        TreeNode t1 = new TreeNode(1);
        TreeNode t2 = new TreeNode(2);
        TreeNode t3 = new TreeNode(3);
        TreeNode t4 = new TreeNode(4);
        TreeNode t5 = new TreeNode(5);
        t1.left=t2;
        t1.right=t3;
        t2.left=t4;
        t2.right=t5;

        System.out.println();
        System.out.println("层次遍历:");
        List<List<Integer>> lists = levelOrder(t1);

        System.out.println("结果:"+lists.toString());

        System.out.println("遍历结果:");

        lists.stream().forEach(li -> {

            li.stream().forEach(ll -> {
                System.out.print(ll.intValue()+" ");
            });

            System.out.println();

        });

    }

   static public List<List<Integer>> levelOrder(TreeNode root) {
       List<List<Integer>> lists = new ArrayList<>();
       //如果直接传入的是空,那就直接返回
       if(root==null)
           return lists;
       //利用队列的特性来存储每层的节点
       LinkedList<TreeNode> queue = new LinkedList<>();
       //默认根节点已经加入到队列
       queue.add(root);
        while(!queue.isEmpty()){
            List<Integer> list = new ArrayList<>();
            int n = queue.size();//记录当前层次的数量
            int index = 0;
            //只要每层遍历的次数符合每层的数量就算是遍历的这同一层的节点。
            while (index<n){
                root = queue.pop();
                //添加到每一层的List中
                list.add(root.val);

                if(root.left!=null) {
                    queue.add(root.left);
                }

                if(root.right!=null) {
                    queue.add(root.right);
                }

                //索引++  遍历同一层次后边的节点
                index++;
            }

            //把每一层的节点加入到最终的List中
            lists.add(list);
        }

        return lists;
    }
}

class TreeNode {
    int val;//每个节点存放的数据
    TreeNode left;//左节点
    TreeNode right;//右节点
    TreeNode(int x) { val = x; }
}

二叉树的锯齿形层次遍历

力扣https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/思路:在上述层次遍历的基础上, 利用level层级判断 返回追加数据的顺序 "头插法"还是"尾插法"

我们可以给每一层都标上一个号。
那我们就从1开始
如果这个层是奇数说明是第一层
如果是偶数说明是第二层
然后层数+1
然后循环执行
当判断是奇数还是偶数的时候执行不同的操作。
如果是奇数,那就把数据使用尾插法插入的list,说白了就是追加数据
如果是偶数,那就吧数据使用头插法插入list
最终就实现了这个题。

核心代码如下:

   static public List<List<Integer>> levelOrder(TreeNode root) {
       List<List<Integer>> lists = new ArrayList<>();

       if(root==null) return lists;

       TreeNode treeNode = root;

       LinkedList<TreeNode> queue = new LinkedList<>();
       queue.add(treeNode);

       int ceng = 1;//记录层次

        while(!queue.isEmpty()){

            List<Integer> list = new ArrayList<>();

            int n = queue.size();

            int index =0;

            while (n>index){

                //出栈
                TreeNode node = queue.pop();

                //说明是第一层 如果第二层是会ceng+1,说明切换到了第二层
                if(ceng%2!=0){
                    //追加数据,也就是尾插法
                    list.add(node.val);
                //说明是二层
                }else{
                    //头插法
                    list.add(0,node.val);
                }
                if(node.left!=null){
                    queue.add(node.left);
                }

                if(node.right!=null){
                    queue.add(node.right);
                }

                index++;
            }

            ceng++;//每一层结束都+1
            lists.add(list);
        }

        return lists;
    }

或者如下:

始终保持队列中顺序为:每层的从左往右

  • 奇数层,从左往右弹出,从右往左添加
  • 偶数层,从右往左弹出,从左往右添加
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null){
            return res;
        }
        ArrayDeque<TreeNode> queue = new ArrayDeque<>();
        queue.add(root);
        int count = 1;
        while(!queue.isEmpty()){
            int size = queue.size();
            List<Integer> curList = new ArrayList<>();
            for(int i = 0;i<size;i++){
                //奇数层,从左往右弹,从右往左添加
                if(count % 2 == 1){
                    TreeNode cur = queue.pollFirst();
                    curList.add(cur.val);
                    //保证结点在队列中的顺序,先加左结点到Last,再加右结点到Last
                    if(cur.left != null){
                        queue.addLast(cur.left);
                    }
                    if(cur.right != null){
                       queue.addLast(cur.right);
                    }
                //偶数层,从右往左弹,从左往右添加
                }else {
                    TreeNode cur = queue.pollLast();
                    curList.add(cur.val);
                    //保证结点在队列中的顺序,先加右结点到First,再加左结点到First
                    if(cur.right != null){
                        queue.addFirst(cur.right);
                    }
                    if(cur.left != null){
                        queue.addFirst(cur.left);
                    }
                }
            }
            if(!curList.isEmpty()){
                res.add(curList);
            }
            count++;
        }
        return res;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值