代码随想录算法训练营第十一天|LeetCode 144. 二叉树的前序遍历、 94. 二叉树的中序遍历、 145. 二叉树的后序遍历、 102. 二叉树的层序遍历

今天开始二叉树!!

二叉树的递归遍历和层序遍历是两种常见的遍历方法。
递归遍历(如前序遍历)通过递归函数实现,从根节点开始,首先访问根节点,然后递归访问左子树和右子树。代码通过检查节点是否为空,并按顺序将节点值添加到结果列表中。
层序遍历利用队列从根节点开始,逐层访问树的每一层节点。每层节点都被依次加入队列,并在访问时将其子节点加入队列,直至遍历完整棵树。
两种方法各有优势,递归遍历简单直观,而层序遍历适用于按层次处理树结构的问题。两种方法也都十分重要,需要熟记于心!

下面先开始说递归遍历
想要写准递归,需遵循递归三部曲进行:
1. 确定递归函数的参数和返回值:确定哪些参数是递归过程中需要处理的,那么就在这个递归函数里加上这个参数,同时还要明确每次递归的返回值是什么类型,从而方便判断递归函数的整体返回类型。
2. 确定终止条件:操作系统也是用一个栈的结构来保存每一层递归信息,如果递归没有终止,操作系统的内存栈必然会溢出。
3. 确定单层的递归逻辑:确定每一层递归需要处理的信息,从而实现重复调用自己达到递归的目的。

接下来以前序遍历为例来说明“递归三部曲”:

  1. 确认递归函数的参数和返回值: 因为要打印前序遍历节点的数值,所以参数需要传入result数组用来存放节点的数值。同时每次递归要处理当前节点,因此节点也应当传入,除此之外就不太需要其他东西了。
public void preorder(TreeNode root, List<Integer> result)
  1. 确定终止条件:在本题中,自然是在节点一层层深挖时,遇到空节点的时候就该返回了,所以如果当前遍历的节点是空就应该直接返回。
if(root == null){
    return;
}
  1. 确定单层递归的逻辑: 前序遍历是 中左右 的顺序,所以在单层递归时,是要先存中间的节点的值,再存左子节点,最后是右子节点。因此对于当前节点来说,就是先存自己的值,然后遍历左节点,最后遍历右节点。
result.add(root.val); // mid
preorder(root.left, result); // left
preorder(root.right, result); // right

具体题目及完整代码如下:

题目链接:144. 二叉树的前序遍历

题目链接:94. 二叉树的中序遍历

题目链接:145. 二叉树的后序遍历

/** //Java 递归遍历中的前序遍历preorder
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

    public void preorder(TreeNode root, List<Integer> result){
        if(root == null){
            return;
        }
        result.add(root.val);
        preorder(root.left, result);
        preorder(root.right, result);
    }
}

中序遍历主要是修改了方法中的添加节点值到数组的时机

class Solution { // Java 递归遍历中的中序遍历 inorder
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }
    public void inorder(TreeNode root, List<Integer> result){
        if(root == null){
            return;
        }
        inorder(root.left,result);
        result.add(root.val); // notice the timing for adding here
        inorder(root.right, result);
    }
}

后序遍历也是同样的道理,只是修改了result数组使用add方法添加节点的值的时机

class Solution { // Java 递归遍历中的后序遍历postorder
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        postorder(root, res);
        return res;
    }
    public void postorder(TreeNode root, List<Integer> result){
        if(root == null){
            return;
        }
        postorder(root.left, result);
        postorder(root.right, result);
        result.add(root.val); // notice the add timing here
    }
}

复杂度分析:
时间复杂度:O(n)。因为每个节点都会被访问一次,然后preorder会被调用一次。
空间复杂度:O(n)/O(log n)。空间复杂度基于递归调用栈的深度,最坏情况下栈的深度等于树的高度h。对于平衡二叉树,树高h大约是log n,所以最坏情况下空间复杂度为O(log n);对于非平衡树,最坏情况下为链表形状的树,树高h = n,所以最坏情况下空间复杂度为O(n)。

中序遍历和后序遍历的代码与前序遍历相同,只是顺序问题,所以复杂度分析一样。


接下来说说迭代法(非递归)来实现二叉树的前中后序遍历:
首先是介绍思路上为什么迭代法可以实现:我们知道递归法是可以实现二叉树的遍历的(上文刚讲过),这种方式也被称为深度优先搜索(Depth-First Search, 简称DFS),而递归的实现就是 每次递归调用都会把函数的局部变量、参数值和返回地址等信息压入调用栈中,然后等递归返回的时候,从栈顶弹出各项参数。因此我们可以使用栈来直接模拟这个递归的过程。

我们先看前序遍历,前序遍历是中左右,所以我们每次先处理中间节点,那就先把中间节点放入栈中,接下来是放右子节点,最后是放左子节点。这是因为栈的结构表现出先进后出的特点,所以先放右子节点后放左子节点 才能保证输出的顺序是 中左右。 完整代码如下:

// Java 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.right != null){
                stack.push(node.right);
            }
            if (node.left != null){
                stack.push(node.left);
            }
        }
        return result;
    }
}

复杂度分析:
时间复杂度:O(n)。 每个节点都被访问一次,并且每个节点都被压入、弹出栈一次
空间复杂度:O(n)/O(log n)。取决于树的形状、高度。道理同递归法。
后序遍历可以是在前序遍历的基础上稍作修改得到,这是因为只需调转while循环中的对于左右节点处理代码,结果就由 中左右 变成 中右左,然后此时再把结果使用reverse反转函数把结果反转后, 就会变成左右中,也就是我们需要的后序遍历了。完整代码如下:

// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.left != null){ // switch
                stack.push(node.left);
            }
            if (node.right != null){ // switch
                stack.push(node.right);
            }
        }
        Collections.reverse(result); // reverse function
        return result;
    }
}

复杂度分析同 上述的迭代法的 前序遍历。

最后是中序遍历,中序遍历不能 由 以上代码的基础上通过一些顺序上的变幻和一些额外方法的使用实现。这是因为刚刚前序遍历时,访问的节点和要处理的节点是同一个节点---都是中间节点。而中序遍历是 左中右,访问的是顶部节点,处理的是底部节点,换句话说就是他得先遍历到树左边的最底部才开始处理节点,因此造成访问节点和处理节点不统一的问题。
所以我们的处理方式是 保留刚才前序遍历的两个核心操作:访问,即遍历节点 和 处理,即将元素放入result数组中;同时借助指针的遍历来帮助访问节点,用栈来处理节点上的元素。 完整代码如下:

class Solution { // Java 中序遍历顺序: 左-中-右 入栈顺序: 左-右
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()){
           if (cur != null){
               stack.push(cur);
               cur = cur.left;
           }else{
               cur = stack.pop();
               result.add(cur.val);
               cur = cur.right;
           }
        }
        return result;
    }
}

复杂度分析同 上述的迭代法的 前序遍历。


接下来是层序遍历,顾名思义 就是要按照树的结构一层一层的遍历出结果。这种方式也被称为广度优先搜索(Breadth-First Search, 简称BFS)。
遇到这样的结构,我们需要使用 队列 来帮助我们解决问题。因为队列先进先出的特点符合我们一层一层遍历二叉树的性质。

题目链接:102. 二叉树的层序遍历

/** // Java 层序遍历
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<List<Integer>> resultList = new ArrayList<>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        checkFun02(root);
        return resultList;
    }

    public void checkFun02(TreeNode root){
        
        if(root == null) return;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        

        
        while(!queue.isEmpty()){

            List<Integer> list = new LinkedList<>();
            int size = queue.size();
            
            
            
            while(size > 0){
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left != null){
                    queue.offer(node.left);
                }
                if(node.right != null){
                    queue.offer(node.right);
                }
                size--;
            }
            resultList.add(list);
        }
        
    }
}

复杂度分析:
时间复杂度:O(n)。每个节点都会访问一次,且每个节点都会被加入和移除队列一次,所以是O(n).
空间复杂度:O(n)。空间复杂度主要取决于resultList的大小,和队列中的值。
队列中会存储当前层和下一层的节点,最坏情况下是处理满二叉树的时候,最后一层的元素约等于n/2.
resultList数组是存放结果的。因为要存放所有的结果,所以是O(n)。
所以综合一下,空间复杂度为O(n)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容