首页 > 编程语言 >代码随想录算法训练营第十四天|二叉树递归遍历、迭代遍历、统一迭代

代码随想录算法训练营第十四天|二叉树递归遍历、迭代遍历、统一迭代

时间:2024-06-09 19:29:25浏览次数:16  
标签:node deque 遍历 迭代 第十四天 null root 节点

二叉树遍历

二叉树主要有两种遍历方式:

  1. 深度优先遍历:先往深走,遇到叶子节点再往回走。深度优先遍历又分:

    • 前序遍历(中、左、右)
    • 中序遍历(左、中、右)
    • 后序遍历(左、右、中)在这里插入图片描述
  2. 广度优先遍历:一层一层的去遍历。(后面讲)

递归遍历

递归三要素

  1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
    List<Integer> res = new ArrayList<>();

    public void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        preOrder(root.left);
        preOrder(root.right);
    }

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

    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        res.add(root.val);
    }

迭代遍历

二叉树遍历的迭代法,可以使用栈来模拟深度遍历DFS,使用队列来模拟广度遍历BFS!

注意:使用双端队列模拟栈时,如果使用了addLast压栈,对应的就该使用pollLast出栈(推荐使用addLast,因为栈后进先出,压栈操作就是从后面压入的),使用了addFirst压栈,对应使用pollFirst出栈。模拟队列时,addLast对应pollFirst(推荐,队尾入队,队头出队),addFirst对应pollLast。

前序遍历

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

    /**
     * 迭代遍历的方法
     * 1.创建一个空栈,将根节点压入栈中。
     * 2.循环执行以下步骤直到栈为空:
     * 2.1弹出栈顶节点,访问该节点。
     * 2.2将右子节点(如果存在)压入栈。
     * 2.3将左子节点(如果存在)压入栈。注意要先压入右子节点,再压入左子节点,这样在弹出时左子节点会先被处理。
     */
    public void preOrder(TreeNode root) {
        //1.root为null,不为null则加入根节点
        if (root == null) {
            return;
        }
        Deque<TreeNode> stack = new LinkedList<>();
        stack.addLast(root);

        //2.使用栈,中右左入栈,中左右出栈(为啥不是右左中入栈?因为先访问根节点,才有后面根节点下的左右节点)
        while (!stack.isEmpty()) {
            //记录栈顶节点,后续要访问它的左右子节点
            TreeNode node = stack.peekLast();
            //弹出栈顶节点
            stack.pollLast();
            //记录栈顶节点值
            res.add(node.val);
            //右孩子入栈
            if (node.right != null) {
                stack.addLast(node.right);
            }
            //左孩子入栈
            if (node.left != null) {
                stack.addLast(node.left);
            }
        }
    }

中序遍历

   /**
     * 中序遍历迭代法
     * 1.初始化栈,将当前节点指向根节点。
     * 2进入循环,循环条件是当前节点不为空或者栈不为空。
     * 2.1在循环中,首先将当前节点以及其所有左子节点依次入栈,直到最左下角的节点(即最左边的叶子节点)。
     * 2.2弹出栈顶节点,访问该节点。
     * 2.3将当前节点指向弹出节点的右子节点。(如果右子节点为空,这时当前节点就会变成上一层节点的父节点,然后继续弹出上一层节点并访问它的右子节点)
     * 2.4重复上述三个步骤,直到栈为空且当前节点为空。
     */
    public List<Integer> inorderTraversal2(TreeNode root) {
        // 中序遍历顺序: 左-中-右 入栈顺序: 左-右
        /**
         *            5
         *        4        6
         *      1   2
         */
        Deque<TreeNode> deque = new ArrayDeque<>();
        List<Integer> result = new ArrayList<>();
        TreeNode cur = root;
        if (root == null) {
            return result;
        }
        while (cur != null || !deque.isEmpty()) {
            if (cur != null) {
                //一直往下遍历二叉树左节点
                deque.addLast(cur);
                cur = cur.left;
            } else {
                //遍历到1的左子结点为null,出栈1
                cur = deque.pollLast();
                result.add(cur.val);//因为一旦没有左孩子,此时的节点就是中节点,就需要进行处理,接着遍历右孩子
                //指针指向1的右子节点,如果也为空继续出栈4
                cur = cur.right;//因为右孩子也为空,说明左中右都访问了,需要向上访问父节点了(对应出栈操作)
            }
        }
        return result;
    }

后续遍历

 /**
     * 1.初始化一个ArrayDeque(双端队列)和一个List(用于存储遍历结果)。
     * 2.如果根节点不为空,将根节点加入到队列中。
     * 3.进入循环,循环条件是队列不为空。
     * 从队列的尾部取出一个节点,将节点的值加入到结果列表中。
     * 如果节点的左子节点不为空,将左子节点加入队列尾部。
     * 如果节点的右子节点不为空,将右子节点加入队列尾部。
     * 4.循环结束后,对结果列表进行翻转(因为是后序遍历,所以要翻转结果)。
     * 5.返回翻转后的结果列表。
     */
    public List<Integer> postorderTraversal2(TreeNode root) {
        // 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
        ArrayDeque<TreeNode> deque = new ArrayDeque<>();
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        deque.addLast(root);
        while (!deque.isEmpty()) {
            TreeNode node = deque.pollLast();
            res.add(node.val);
            if (node.left != null) {
                deque.addLast(node.left);
            }
            if (node.right != null) {
                deque.addLast(node.right);
            }

        }
        Collections.reverse(res);
        return res;
    }

统一迭代

针对三种遍历方式,使用迭代法是可以写出统一风格的代码!

前序遍历(中左右)

在这里插入图片描述

    //前序遍历,中左右,入栈顺序右左中
    public List<Integer> preorderTraversal(TreeNode root) {
        ArrayList<Integer> result = new ArrayList<>();
        //在Java的ArrayDeque中,不允许添加null元素!!!!!!
        Deque<TreeNode> deque = new LinkedList<>();
        if (root != null) {
            deque.addLast(root);//加入结点5
        }
        while (!deque.isEmpty()) {
            TreeNode node = deque.peekLast();//记录结点5 //记录结点4
            if (node != null) {
                deque.pollLast();//弹出结点5 //弹出结点4
                if (node.right != null) {
                    deque.addLast(node.right);//加入5的右孩子6  //加入4结点的右孩子2
                }
                if (node.left != null) {
                    deque.addLast(node.left);//加入5的左孩子4  //加入4结点的左孩子1
                }
                deque.addLast(node);//加入结点5  (可以发现此时结点入栈顺序是右->左->中,出栈就是中左右) //加入结点4
                deque.addLast(null);//加入空节点 //加入空节点
            } else {
                deque.pollLast();//遇到null就出栈空结点 //遇到null就出栈空节点
                node = deque.pollLast();//此时栈顶结点是5,结点5出栈 //此时栈顶是4,结点4出栈
                result.add(node.val);//记录5 //记录4 .....
            }
        }
        return result;
    }

中序遍历(左中右)

    //中序遍历:左中右
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Deque<TreeNode> deque = new LinkedList<>();
        if (root != null) {
            deque.addLast(root);
        }
        while (!deque.isEmpty()) {
            TreeNode node = deque.peekLast();
            if (node != null) {
                // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                deque.pollLast();
                // 添加右节点(空节点不入栈)
                if (node.right != null) {
                    deque.addLast(node.right);
                }
                // 添加中节点
                deque.addLast(node);
                // 中节点访问过,但是还没有处理,加入空节点做为标记.
                deque.addLast(null);
                // 添加左节点(空节点不入栈)
                if (node.left != null) {
                    deque.addLast(node.left);
                }
            } else {
                // 只有遇到空节点的时候,才将下一个节点放进结果集
                // 将空节点弹出
                deque.pollLast();
                // 重新取出栈中元素
                node = deque.pollLast();

                // 加入到结果集
                result.add(node.val);
            }
        }
        return result;
    }

后序遍历(左右中)

 //后序遍历:左右中
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Deque<TreeNode> deque = new LinkedList<>();
        if (root != null) {
            deque.addLast(root);
        }
        while (!deque.isEmpty()) {
            TreeNode node = deque.peekLast();
            if (node != null) {
                deque.pollLast();
                deque.addLast(node);
                deque.addLast(null);
                if (node.right != null) {
                    deque.addLast(node.right);
                }
                if (node.left != null) {
                    deque.addLast(node.left);
                }
            } else {
                deque.pollLast();
                node = deque.peekLast();
                deque.pollLast();
                result.add(node.val);
            }
        }
        return result;
    }

标签:node,deque,遍历,迭代,第十四天,null,root,节点
From: https://blog.csdn.net/weixin_43903745/article/details/139442735

相关文章

  • 答题判题程序终版与家居强电电路模拟程序两次迭代
    目录:一)前言二)设计与分析三)踩坑心得四)改进建议五)总结一.前言(1)答题判题程序-4:【1】知识点:正则表达式,判题的逻辑思维能力,数据形式转换。【2】题量:很大【3】难度:很难是前三次答题判题程序迭代优化的最终形态,难度较高,它对于类的种类的个数已经类与类之间的关系的理解要求更......
  • Keil一键添加.c文件和头文件路径脚本--可遍历添加整个文件夹
    最近想移植个LVGL玩玩,发现文件实在是太多了,加的手疼都没搞完,实在不想搞了就去找脚本和工具,基本没找到一个。。。。。。主要是自己也懒得去研究写脚本,偶然搜到了一个博主写的脚本,原博客地址:https://blog.csdn.net/riyue2044/article/details/139424599但是有以下问题:1.这个脚本......
  • 【C++/STL】list(常见接口、模拟实现、反向迭代器)
     ......
  • C++:Traits编程技法在STL迭代器中的应用
    文章目录迭代器相应型别Traits(特性)编程技法——STL源代码门钥迭代器相应型别一:value_type迭代器相应型别二:difference_type迭代器相应型别三:reference_type迭代器相应型别四:pointer_type迭代器相应型别五:iterator_category以`advanced()`为例取消单纯传递调用的函数以`......
  • 144. 二叉树的前序遍历
    /***Definitionforabinarytreenode.*typeTreeNodestruct{*Valint*Left*TreeNode*Right*TreeNode*}*/funcpreorderTraversal(root*TreeNode)[]int{returnpre2(root)//vals:=[]int{}//pre1(root,&val......
  • Mat的lambda方式像素高效遍历(C++11)
    Mat的lambda方式像素高效遍历(C++11)文章目录Mat的lambda方式像素高效遍历(C++11)前言一、Mat的lambda方式像素高效遍历二、代码实现总结前言图像遍历是图像处理中的经典操作,快速高效的进行像素遍历对性能的提升至关重要。一、Mat的lambda方式像素高效遍历OpenCV4......
  • 迭代与递归--你被递归搞晕过吗?
    前言算法中会经常遇见重复执行某个任务,那么如何实现呢,本文将详细介绍两种实现方式,迭代与递归。本文基于Java语言。一、迭代迭代(iteration),就是说程序会在一定条件下重复执行某段代码,直到条件不再满足。在Java语言中,可以理解为就是循环遍历,Java中有多种遍历方式,如for......
  • 螺旋转动,矩阵的舞蹈:JavaScript中实现螺旋矩阵遍历算法
    螺旋转动,矩阵的舞蹈:JavaScript中实现螺旋矩阵遍历算法基础概念:什么是螺旋矩阵?核心算法解析示例一:基础螺旋矩阵遍历算法解析进阶技巧示例二:动态生成螺旋矩阵技巧点实战与性能优化问题与解决:大矩阵处理结语与讨论在编程的奇幻世界里,数组与矩阵是构筑数字城堡的基石......
  • [OI] 指针与迭代器
    取地址与解引用一般来说,我们有一个取地址符&可以返回该变量的地址.intmain(){ inta; cout<<&a;}0x6ffe1c如果我们现在有一个地址,我们还可以对它进行解引用*,来返回这个地址上的值.intmain(){ inta=5; cout<<&a<<endl; cout<<*(&a);}0x6ffe1c5从这里可以......
  • 可迭代对象-迭代器-生成器
    可迭代对象能被for循环遍历的元素lists=[1,2,3,4]foriinlists:print(i)生成器是一种特殊的变量斐波那契数列生成器defget_data(num):x=0y=1foriinrange(num):x,y=y,x+yyieldx#返回的是一个yield生成器迭代器能被next函数调用并不断返回下一个值的......