草庐IT

数据结构与算法----详解二叉树的遍历(迭代、递归)

小鱼干儿♛ 2024-02-20 原文

文章目录

  • ❤️ 作者简介:大家好我是小鱼干儿♛是一个热爱编程、热爱算法的大三学生,蓝桥杯国赛二等奖获得者
  • 🐟 个人主页https://blog.csdn.net/qq_52007481
  • 个人社区【小鱼干爱编程】
  • 🔥 算法专栏算法竞赛进阶指南
  • 💯 刷题网站:虽然市面上有很多的刷题网站,但是里面的题又多又杂,不适合系统性的提高算法能力,如何挑选一个适合自己的刷题网站呢,这里推荐一款我常用的刷题网站 👉牛客网

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。--------百度百科

二叉树的遍历是使用二叉树的最基础的操作,常见的几种遍历方式有,前序遍历,中序遍历,后续遍历,层次遍历,这里所说的前、中、后是表示父节点被访问的次序,层次节点是表示一层一层,从左往右访问节点

实现二叉树的类

因为这里是讲解的二叉树的几种遍历形式,二叉树添加的数据的部分就不再写了,
结果测试我们就去刷题网站测试:牛客网

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

每种遍历都有两种方式递归、迭代,因为递归不仅容易出现超过最大递归深度,而且非常浪费计算机资源,因此在实际使用过程中我们应该尽量的避免递归,在大多数情况下递归都有对应的迭代版本。

在实现二叉树的迭代遍历的过程中,使用了栈的思想,我们会用一个栈来存储节点的位置

前序遍历

前序遍历:父节点 > 左节点 > 右节点

  • 递归方式
arr = []
def preOrder(root):
    if root==None:    # 函数停止的条件,
        return 
    arr.append(root.val)   # 先存放父节点的元素
    preOrder(root.left)    # 将左节点当作一个树,继续执行 
    
    preOrder(root.right)   # 将右节点当作一个树,继续执行 
  • 迭代方式

栈的作用就是存储还未被遍历节点的指针,等后面使用的时候在取出
工作流程:

  • 3进栈,2进栈、输出1的值,2出栈
  • 5进栈,4进栈、输出2的值,4出栈
  • 7进栈,输出4的值,7出栈
  • 输出7的值,3出栈
  • 6进栈,输出3的值,6出栈
  • 8进栈,输出6的值,8出栈
  • 输出8的值
stack = []   # 栈,用来存储节点的指针
while root:  # 当二叉树存在时执行循环
    if root.right !=None:  # 因为是先序遍历,所有右边的节点要先进栈
        stack.append(root.right)
    if root.left!=None:
        stack.append(root.left)
    print(root.val)       #  每循环一个节点就输出一个值,可以根据需要调整
    if len(stack) == 0:
        break
    else:
        root = stack.pop()   # 弹出栈顶的元素 

中序遍历

中序遍历:中节点 > 父节点 > 右节点

  • 递归方式
arr = []
def medOrder(root):
    if root==None:
        return 
    medOrder(root.left)     # 先找到最左叶子节点,
    arr.append(root.val)    # 
    medOrder(root.right)
  • 迭代方式
    先找到最左节点,从最左节点开始遍历二叉树

工作流程:

  • 1进栈
  • 2进栈
  • 4进栈
  • 7进栈
  • 7出栈 输出7的值
  • 4出栈 输出4的值
  • 2出栈,输出2的值,
  • 5进栈
  • 5出栈,输出5的值
  • 1出栈,输出1的值
  • 3进栈
  • 3出栈,输出3的值
  • 6进栈
  • 8进栈,输出8的值
  • 6出栈,输出6的值
stack = []
while True:
    if root:
        stack.append(root)  # 将节点存入栈中
        root = root.left
    elif len(stack)>0:
        root = stack.pop()  # 取出栈顶的元素,第一次取到的是最左节点,
        print(root.val)
        root = root.right   
    else:
        break

后序遍历

后序遍历:左节点 > 右节点 > 父节点

  • 递归方式
arr = []
def postOrder(root):
    if root==None:
        return 
    postOrder(root.left)
    postOrder(root.right)
    arr.append(root.val)
  • 迭代方式
    因为父节点在左右节点的之间,所以用迭代的方式实现后续遍历,比较困难,需要一个额外的数组用来存放左右两边都遍历过的父节点。
  • 1进栈
  • 2进栈
  • 4进栈
  • 7进栈
  • 7出栈,输出7的值
  • 4出栈,输出4的值
  • 2出栈,左节点还未被遍历,2重新进栈
  • 5进栈
  • 5出栈, 输出5的值
  • 2出栈,输出2的值
  • 1出栈,左节点还未被遍历,1重新进栈
  • 3进栈
  • 6进栈
  • 8进栈
  • 8出栈,输出8的值
  • 6出栈,输出6的值
  • 3出栈,输出3的值
  • 1出栈,输出1的值
stack = []  # 栈
lst = []  # 存放左右两边都被遍历过父节点
while True:
    if root:
        stack.append(root)   # 将节点入栈
        root = root.left     # 将左节点当作新的二叉树
    elif len(stack)>0:
        root = stack.pop()    # 弹出栈顶的元素
        if root.right:   # 查看该节点是否有右节点,如果有判断是否被遍历过
            if root in lst:  # 该节点的左右两边都被遍历
                lst.remove(root)  # 从该数组中删除,(不删除也可以,删除会减少内存空间)
                print(root.val)   # 输出该值
                root = None       # 因为都被访问过了,就类似一个空树
            else:   # 该节点的右节点还未被遍历
                stack.append(root)  # 重新将该节点入栈
                lst.append(root)    # 其实此时可能并没有遍历,但是因为栈的特性,我们可以知道遍历右节点一定早于父节点所以可以添加到左右两边都被遍历的数组中
                root = root.right   # 将右节点当作一个新的二叉树,继续循环               
        else:
            print(root.val)
            root = None      # 当没有右节点,则代表树为空
    else:
        break

层次遍历


层次遍历只有迭代版本
层次遍历使用的就不是栈, 使用的是队列(先进先出)

算法执行的流程

  • 1入队
  • 1出队,输出1的值,2入队,3入队
  • 2出队,输出2的值,4入队,5入队
  • 3出队,输出3的值,6入队
  • 4出队,输出4的值,7入队
  • 5出队,输出5的值
  • 6出队,输出6的值,8
  • 7出队,输出7的值
  • 8出队,输出8的值
queue = []
if root == None:
    return []
queue.append(root)
while len(queue)>0:
    root = queue.pop(0)
    print(root.val)
    if root.left != None:
        queue.append(root.left)
    if root.right != None:
        queue.append(root.right)

练习题:

👉直达牛客,快人一步


题解1,使用递归解题

class Solution:
    def threeOrders(self , root: TreeNode) -> List[List[int]]:
        # write code here
        # 先序遍历
        arr = [[],[],[]]
        def preOrder(root):
            if root==None:
                return
            arr[0].append(root.val)
            preOrder(root.left)
            preOrder(root.right)
        preOrder(root)
        def medOrder(root):
            if root==None:
                return
            medOrder(root.left)
            arr[1].append(root.val)
            medOrder(root.right)
        medOrder(root)
        def postOrder(root):
            if root==None:
                return
            postOrder(root.left)
            postOrder(root.right)
            arr[2].append(root.val)
        postOrder(root)
        return arr

题解2,简化递归,
从第一个我们能够发现,前序后续的算法非常相似,只是顺序有区别,我们其实可以将这些同时放在一个函数里面。

class Solution:
    def threeOrders(self , root: TreeNode) -> List[List[int]]:
        arr = [[],[],[]]
        def Order(root):
            if root==None:
                return
            arr[0].append(root.val)
            Order(root.left)
            arr[1].append(root.val)
            Order(root.right)
            arr[2].append(root.val)
        Order(root)
        return arr

题解3 使用迭代
使用迭代的,虽然看着代码非常复杂,但是随着数据量的增多,效率会比迭代的越来越强。


class Solution:
    def threeOrders(self , root: TreeNode) -> List[List[int]]:
        # write code here
        # 先序遍历
        base = root
        arr = [[],[],[]]
        stack = []
        while root:
            if root.right !=None:
                stack.append(root.right)
            if root.left!=None:
                stack.append(root.left)
            arr[0].append(root.val)
            if len(stack) == 0:
                break
            else:
                root = stack.pop(len(stack)-1)
                
        root = base
        stack = []
        while True:
            if root:
                stack.append(root)
                root = root.left
     
            elif len(stack)>0:
                root = stack.pop()
                arr[1].append(root.val)
                 
                root = root.right
            else:
                break
 
        root = base
        stack = []
        lst = []
        while True:
            if root:
                stack.append(root)
                root = root.left
            elif len(stack)>0:
                root = stack.pop()
                if root.right:
                    if root in lst:
                        lst.remove(root)
                        arr[2].append(root.val)
                        root = None
                    else:
                        stack.append(root)
                        lst.append(root)
                        root = root.right
                         
                else:
                    # 取值
                    arr[2].append(root.val)
                    root = None
            else:
                break
        return arr

总结

二叉树遍历的遍历,递归的函数比较容易写,但是太浪费计算机的资源,所以要改写为迭代的方式实现。我们在使用迭代实现的时候,应用了栈的思想,层次遍历使用了队列的思想

补充:因为这篇文章是需要有一定的基础的,大家如果对栈和队列不太明白的可以看我这篇文章数据结构与算法----栈和队列(Stack & Queue)
如果文章有哪些问题也欢迎大家大家指正

有关数据结构与算法----详解二叉树的遍历(迭代、递归)的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐