首页 > 其他分享 >Day13 二叉树Part1 遍历

Day13 二叉树Part1 遍历

时间:2024-07-29 13:07:13浏览次数:15  
标签:node 遍历 res stack Part1 二叉树 Day13 root 节点

递归遍历

要理解递归,可以借助二叉树的递归遍历来理解。下面是二叉树递归遍历,以这个为例,来阐述下递归的原理。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def universalTraversal(self, root: Optional[TreeNode]) -> List[int]:
        def dfs(cur: Optional[TreeNode]):
            # 1
            if not cur:
                return 
            # 1
            dfs(cur.left)
            # 2
            dfs(cur.right)
            # 3
        dfs(root)

原理

如上所示,dfs是一个递归函数,它表示以某个节点开始深度搜索(包含它及其子树)。而universalTraversal传入参数root并调用dfs,表示深度搜索root为根的这棵树。那么,如何理解这个dfs呢?注意到代码注释出的1,2,3,它们表示当前节点的调用时机,其中1是指刚刚遍历到当前节点时,2是指刚刚从该节点的左子树返回时,3时指刚刚从这个节点的右子树返回时。某个节点执行当层的dfs时,一共可以处理当前节点的时机就是这三个位置。

那么,何为递归呢?我的理解是,‘递’就是向下搜索的过程,比如上述遍历中,向左,右子树遍历就是‘递’,而从左,右子树返回就是归(2,3位置)。而我们不可能无限的'递'下去,总有一个尽头,这就引入了递归基或者说终止条件。什么是递归基呢?就是'递'到最后(有些问题是到叶子节点可以解决,有些问题是到叶子节点的左右子节点(空)可以解决),上例中的语句就是cur为空的时候直接返回。 所以由前面的思考可知,我们可以将一个原问题拆分为子问题,然后继续向下拆分,直到拆分到能够直接解决的问题,再一层层返回上面。这也是树形DP的基础。

此外,我们知道使用递归实际上隐藏了一个数据结构,就是在递的过程中会进行函数的压栈,归的过程中之前的函数返回,即出栈,直到当前层的递归函数。因此递归遍历使用迭代法实现时,一定离不开栈。

如何使用递归

那么在实际应用中,我们应该如何使用递归呢?实际上,只需要确定单层递归的逻辑和确定递归基。确定单层递归的逻辑,就是考虑当前节点或者当前的问题,如何分解为规模相当的子问题,然后将结果收集到作用域更大的变量(通过更大范围的变量如列表收集遍历过程中的所有所需信息)或者当前层的递归函数中(返回结果,以便继续给上层递归函数使用)。确定递归基就是确定终止条件。
以先序遍历为例,实际上就是上述通用遍历中,多了个列表记录节点的过程,只是节点处理(加入到列表中)的时机是第一次访问到节点的时候,即根左右。中序后序只是处理节点的时机不同,不再赘述。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(cur: Optional[TreeNode]):
            # 1
            if not cur:
                return 
            # 1
            res.append(cur.val)
            # 1
            dfs(cur.left)
            # 2
            dfs(cur.right)
            # 3
        
        dfs(root)
        return res

递归遍历如何用迭代

因为每个加入的节点不是遇到就处理,而是可能在之后才处理(即使是先序,之前加入的某节点的右节点也是在该节点的左子树处理完后才处理的),所以使用栈来实现。

先序遍历

栈不为空时,先加入右节点,再加入左节点。保证出栈时顺序为左右

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res = []
        stack = []
        stack.append(root)
        while stack:
            node = stack.pop()
            res.append(node.val)
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return res

后序遍历

后序遍历为 左右根 ,与根右左逆序,观察顺序,先序遍历为根左右,因此利用先序遍历的代码做简单调整即可。

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res = []
        stack = []
        stack.append(root)
        while stack:
            node = stack.pop()
            res.append(node.val)
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
        res[:] = res[::-1]        
        return res

中序遍历

1 加入到栈中,一直遍历到最左
2 处理当前元素(当前最左),转向最左的右子树,重复1

栈为空只可能是初始状态和结束状态。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root: return []
        res = []
        stack = []
        while(stack or root):
            if root:
                stack.append(root)
                root = root.left
            else:
                root = stack.pop()
                res.append(root.val)
                root = root.right
        return res

层序遍历

层序遍历一层一层的遍历节点,采用的辅助结构是队列。之前的先中后序遍历对应dfs,而层序遍历对应的是bfs。
队列不为空时处理整体,size用来处理每层(知道当前层的个数)

def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root: return []
        res = []
        from collections import deque
        queue = deque()    
        queue.appendleft(root)
        while queue:
            size = len(queue)
            layerNodes = []
            while size:  
                node = queue.popleft()
                layerNodes.append(node.val)
                size = size-1
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            res.append(layerNodes)
        return res

标签:node,遍历,res,stack,Part1,二叉树,Day13,root,节点
From: https://www.cnblogs.com/haohaoscnblogs/p/18329865

相关文章

  • LeetCode654. 最大二叉树
    题目链接:https://leetcode.cn/problems/maximum-binary-tree/description/题目叙述给定一个不重复的整数数组nums。最大二叉树可以用下面的算法从nums递归地构建:创建一个根节点,其值为nums中的最大值。递归地在最大值左边的子数组前缀上构建左子树。递归地在最大......
  • 数据结构-二叉树、堆、图
    一、线索二叉树规律:在有n个节点的链式二叉树中必定存在n+1个空指针链式二叉树中有很多的空指针,可以让这些空指针指向前一个节点\后一个节点,从而在有序遍历(中序遍历)二叉树时,不需要使用递归而通过循环即可以完成,并且效率要比递归快得多一定是搜索二叉树线索二叉树的结构typ......
  • LeetCode222.完全二叉树的节点个数
    题目链接:https://leetcode.cn/problems/count-complete-tree-nodes/description/题目叙述给你一棵完全二叉树的根节点root,求出该树的节点个数。完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该......
  • 数据结构——链式二叉树(C语言版)
    链式二叉树的结构⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址。                                ......
  • (leetcode学习)236. 二叉树的最近公共祖先
    给定一个二叉树,找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树T的两个节点p、q,最近公共祖先表示为一个节点x,满足x是p、q的祖先且x的深度尽可能大(一个节点也可以是它自己的祖先)。”示例1:输入:root=[3,5,1,6,2,0,8,nul......
  • 数据结构-二叉树(顺序结构)
    引言顺序结构存储就是使⽤数组来存储,⼀般只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。一、堆的概念将一个元素集合k里,所有数据按照完成二叉树的顺序存储方式存储。并且数组中的元素,满足以下关系i=0、1、2...,则称为......
  • leetcode105. 从前序与中序遍历序列构造二叉树,步骤详解附代码
    leetcode105.从前序与中序遍历序列构造二叉树给定两个整数数组preorder和inorder,其中preorder是二叉树的先序遍历,inorder是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例1:输入:preorder=[3,9,20,15,7],inorder=[9,3,15,20,7]输出:[3,9,20,nul......
  • 【数据结构】二叉树
    目录二叉树特殊二叉树二叉树的性质二叉树的模拟实现存储结构接口实现内部类遍历前序遍历中序遍历后序遍历遍历确定二叉树层序遍历获取节点个数获取叶子节点的个数获取第K层节点的个数获取二叉树的高度检测值为value的元素是否存在判断一棵树是不是完全二叉树练习链接......
  • 二叉树的遍历
    提纲挈领的说,先序中序后序的遍历区别在于遍历根节点的时机。 1.先序        1.1递归形式 遍历过程为:①访问根结点;②先序遍历其左子树;③先序遍历其右子树。voidPreOrderTraversal(BinTreeBT){if(BT){printf(“%d”,BT->Data);......
  • 二叉树及其存储实现C语言(附上源码)
    1.什么是二叉树        二叉树是一种特殊的树型结构,其特点是每个结点至多只有两棵子树(即二叉树不存在度大于二的结点),并且二叉树的子树有左右之分,次序不可颠倒【有序树】。 2.二叉树的定义二叉树T:一个有穷的结点集合。    -这个集合可以为空;    -......