基础知识:二叉树

1. 前言

写这篇文章的目的主要是记录下自己在 leetcode上刷二叉树上的一点心得,掌握下面的几种二叉树遍历对大家解题速度以及解题技巧有很大的提升,网上关于 leetcode 刷题技巧的文章已经很多很多了,这里我用自己最通俗、最简单、最真实的经历来记录我的刷题过程。

2. 二叉树的遍历

二叉树的遍历主要有递归 以及 迭代这两种方法,对于我目前刷题的感觉来说一些常规题目使用递归是非常快的,但是有些题目我们需要使用迭代的方式去解决。

2.1 递归法

递归解法是比较常用而且容易理解的方法,只要理解了思路就能够很好的去使用他。
**比如前序遍历:**前序遍历的递归方法就是:根、左、右,先打印根节点,只有再去寻找他的左子树,左子树找到末尾的时候返回再找又指数,直到完成。

2.1.1 前序遍历

前序遍历的规则是:根左右

public static void PreOrderRecur(TreeNode root){
	if(root == null) return;
	System.out.println(root.data);
	PreorderRecur(root.left);
	PreorderRecur(root.right);
}

2.1.2 中序遍历

中序遍历的规则是:左根右

public static void InOrderRecur(TreeNode root){
	if(root == null) return;
	InorderRecur(root.left);
	System.out.println(root.data);
	InorderRecur(root.right);
}

2.1.3 后序遍历

后序遍历的规则是:左右根

public static void PostOrderRecur(TreeNode root){
	if(root == null) return;
	PostorderRecur(root.left);
	PostorderRecur(root.right);
	System.out.println(root.data);
}

2.2 迭代法

迭代方法在解决二叉树一些求叶子节点、二叉树的深度等问题中显得尤为重要,目前对于这一类问题我用的比较多的是使用层次遍历去解决的,所以层次遍历我认为是需要着重掌握的。此外,迭代法一般会与栈或者队列结合起来的,所以我们下面讲的东西都是基于栈的使用。

2.2.1 前序遍历

⼆叉树的前序遍历顺序是根-左-右。我们可以采⽤⼀个栈来辅助,我们把先序遍历的结果放到⼀个ArrayList 容器中作为返回值,具体步骤如下:
1、把⼆叉树的根节点 root 放进栈。
2、如果栈不为空,从栈中取出⼀个节点,把该节点放⼊容器的尾部;如果该节点的右⼦树不为空,则把有节点放⼊栈;如果该节点的左⼦树不为空,则把左⼦树放⼊栈中。
3、⼀直重复步骤 2 ,直到栈为空,此时遍历结束。

static List<Interger> PreOrderTraversal(TreeNode root){
	// 初始化一个list集合
	List(Interger) res= new ArrayList<>();
	// 初始化一个栈
	Stack<TreeNode> stack = new Stack();
	// 判断二叉树是不是一棵空树
	if(root == null) return res;
	stack.push(root);//首先把根节点放入栈中
	while(!stack.isEmpty(){
	//当栈中不为空的时候,执行以下操作
	TreeNode temp = stack.pop();//取出栈中的节点,用一个临时变量保存
	res.add(temp.val);//将变量的值加入结果队列中
	if(tmp.right!=null)
		stack.push(tmp.right);//如果该节点的右⼦树不为空,则把有节点放⼊栈
	if(tmp.left!=null)
		stack.push(tmp.left);//如果该节点的左⼦树不为空,则把左⼦树放⼊栈中。
		}
	return res;
}

这里我们可以给大家看下 leetcode官网上给的参考答案,然后我在来说下我们提供的这份代码的巧妙之处。

// Leetcode 官方提供的参考答案
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }
}

我们给的代码在判断栈不为空,然后依次访问右节点,左节点,这里我们会问:前序遍历不是根左右,不应该是先访问左节点在访问右节点?这里我们要注意我们使用的是栈,栈的特点是:先进后出,如果我们先访问左节点那么最后的顺序就会跟我们理论上的不一致,所以才那样写。在对比下leetcode给的代码,虽然理解起来不难,但是总感觉没有那么方便的写。

2.2.2 中序遍历

⼆叉树的中序遍历顺序是左-根-右。我们可以采⽤⼀个栈来辅助,我们把中序遍历的结果放到⼀个ArrayList 容器中作为返回值,具体步骤如下:
1、进⼊ while 循环,接着把根节点及其所有左⼦节点放⼊栈中。
2、从栈中取出⼀个节点,把该节点放⼊容器的尾部;如果该节点的右⼦节点不为空,则把右⼦节点及
其右⼦节点的所有左⼦节点放⼊队列。
3、⼀直重复步骤 2 ,直到栈为空并且当前节点也为空则退出循环。

public List<Integer> InOrderTraversal(TreeNode root){
    List<Integer> res = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    while (root != null || !stack.isEmpty())
    {
        if(root != null){
            stack.push(root);
            root=root.left;
        }
        else{
            root = stack.pop();
            res.add(root.data);
            root=root.right;
        }
    }
    return res;
}

这里不需要在while循环的外面先将根节点存入,因为当时我没考虑清楚,直接这样做,导致根节点重复加入,具体的细节大家可以画个图体会下就会领悟到其中的奥秘。

2.2.3 后序遍历

宽搜+逆序输出 = 后序遍历
⼆叉树的后序遍历顺序是左-右-根。我们可以采⽤⼀个栈来辅助,不过它和前序遍历以及中序遍历还是有点区别的,我们把后序遍历的结果放到⼀个 LinkedList 容器中作为返回值,具体步骤如下:
1、把⼆叉树的根节点 root 放进栈。
2、如果栈不为空,从栈中取出⼀个节点,把该节点插⼊到容器的头部。;如果该节点的左⼦树不为空,则把该左⼦树放⼊栈中;如果该节点的右⼦树不为空,则把右⼦树放⼊栈中。
注意,之前的前序遍历和中序遍历,我们都是⽤ ArrayList 容器,并且是把节点插⼊到容器的尾部,这就是后序遍历的不同点。
3、⼀直重复步骤 2 直到栈为空,此时遍历结束

public List<Integer> postOderTraversal(TreeNode root) {
    LinkedList<Integer> res = new LinkedList<>();// 注意,采⽤链表
    Stack<TreeNode> stack = new Stack<>();
    if(root == null)
        return res;
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode tmp = stack.pop();// 注意,是放在第⼀个位置
        res.addFirst(tmp.val); // 每次将元素添加到第一个位置
        if(tmp.left != null)
            stack.push(tmp.left);
        if(tmp.right != null)
            stack.push(tmp.right);
    }
        return res;
    }

这里我们使用的是LinkedList 而没有使用ArrayList,因为LinkedList有个方法addFirst():作用就是每次将添加的元素放到第一个位置,先进来的元素会依次排在后面,如果没有使用LinkedList而是使用的是ArrayList,其实我们也可以将res.addFirst(tmp.val); 改为 res.add(0, tmp.val); 道理都一样。

2.2.4 层次遍历

层次遍历的规则是先将每一层遍历完在遍历下一层,这里我们需要借助队列来辅助。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
    	
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        // 判断是否为空树
        if(root == null){
            return ans;
        }
        // 初始化队列
        Queue<TreeNode> queue = new LinkedList<>();
        // 定义一个空指针
        TreeNode curr = null;
        // 将根节点入队
        queue.offer(root);
        // 队列不为空,执行下一步
        while(!queue.isEmpty()){
            List<Integer> res = new ArrayList<>();
            // 获取当前队列的长度
            int level = queue.size();  
            // 若队列长度大于0 执行下一步 
            while(level > 0){
            	// curr指向队首元素
                curr = queue.poll();
                // 将队首元素添加进去
                res.add(curr.val);
                // 当前节点左子树不为null,入队
                if(curr.left != null){
                    queue.offer(curr.left);
                }
                // 当前节点右子树不为null 入队
                if(curr.right != null){
                    queue.offer(curr.right);
                }
                // 每遍历一个节点出队一个元素,长度减1
                level --;
            }
            ans.add(res);
        }

        return ans;
    }
}

3. 相关习题

94.二叉树的中序遍历
102.二叉树的层序遍历
144.二叉树的前序遍历
145.二叉树的后序遍历

4. 参考文献

史上最全二叉树遍历详解(Java实现,原理相同)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值