通用算法 - [树结构] -二叉树的一些基本操作(二)

1、前言

数组、链表、二叉树、动态规划、栈与队列是面试中常考的知识点,而在这几个知识点中,二叉树属于难度比较大的部分,因此在这里总结一下二叉树常考的操作,包括:
(1)二叉树的递归遍历;
(2)二叉树的非递归遍历;
(3)求二叉树叶子节点的个数;
(4)求二叉树的深度;
(5)二叉树的子结构;
(6)求二叉树中和为某个值的路径;
(7)求二叉树中的最大路径和;
(8)二叉树的镜像;
(9)判断某个序列是否是二叉搜索树的后序遍历序列;
(10)二叉搜索树和双向链表的相互转换;
(11)树中两个结点的最近祖先;
(12)二叉树搜索树的插入与删除;
(13)堆的插入与删除;
(14)判断一棵树是不是平衡二叉树;
(15)平衡二叉树的调整;

2、二叉树的基本操作

(5)二叉树的子结构

基本思想:判断二叉树A中是否存在某个子结构,该子结构和二叉树B相同。
比如树A和树B分别如下所示:

二叉树A:
在这里插入图片描述
二叉树B:
在这里插入图片描述
当要在二叉树 A A A中查找某个子结构 B B B时,需要两步:第一步,在树 A A A中找到与树 B B B的根节点相同的节点 B ′ B' B;第二步,判断以 B ′ B' B为根节点的子树是否与树 B B B相同。

对于第一步,在树 A A A中找到与树 B B B根节点相等的节点 B ′ B' B,我们可以
(1) 先比较 A A A的某个节点 B ′ B' B和树 B B B的根节点值是否相等;如果相等,判断以 B ′ B' B为根节点的子树是否和树 B B B相同,并将结果记为 i s e x i s t isexist isexist
(2)如果 i s e x i s t = F a l s e isexist=False isexist=False,递归地判断 B ′ B' B的左子树是否和 B B B相同,更新 i s e x i s t isexist isexist的值;
(3)如果 i s e x i s t = F a l s e isexist=False isexist=False,递归地判断 B ′ B' B的右子树是否和 B B B相同,再一次更新 i s e x i s t isexist isexist的值。

对于第二步,判断以 B ′ B' B为根节点的子树和树 B B B是否相同,我们可以
如果 B B B为空返回 True;
如果 B ′ B' B为空,返回False;
如果 B B B B ′ B' B的值不相等,返回False;
比较 B B B的左子树 B ′ B' B的左子树是否相同,结果记为left_equal;
比较 B B B的左子树 B ′ B' B的右子树是否相同,结果记为right_equal;
返回left_equal && right_equal;

代码实现

def FindNode(treenodeA,treenodeB):

    isexist = False

    if treenodeA != None and treenodeB != None:

        if treenodeA.val == treenodeB.val:

            isexist = IsEqual(treenodeA,treenodeB)

        if isexist == False:

            isexist = FindNode(treenodeA.left,treenodeB)

        if isexist == False:

            isexist = FindNode(treenodeA.right,treenodeB)

    return isexist
    


def IsEqual(treenode1,treenode2):

    if treenode2 == None:

        return True

    if treenode1 == None:

        return False
    
 
    if treenode1.val != treenode2.val:

        return False
        
    left_equal = IsEqual(treenode1.left,treenode2.left)

    right_equal = IsEqual(treenode1.right,treenode2.right)

    return left_equal and right_equal

(6)求二叉树中和为某个值的路径

基本思路:二叉树的路径指的是从根节点到叶节点所经过节点形成的序列。我们使用一个栈来保存从根节点到当前节点的路径,如果当前节点为叶节点,则栈中的节点构成一条路径。每当首次访问一个节点时,将该节点进栈,当该节点的所有子节点都访问过之后,将该节点从栈中弹出。

代码实现

def PathSumEqualtoN_Recursion(all_path,rootnode,path,number):

    if rootnode == None:

        return

    path.append(rootnode)

    if rootnode.left == None and rootnode.right == None:

        nodeval = [node.val for node in path]

        if sum(nodeval) == number:

            all_path.append(nodeval)

    PathSumEqualtoN_Recursion(all_path,rootnode.left,path,number)

    PathSumEqualtoN_Recursion(all_path,rootnode.right,path,number)

    path.pop()

    return

def PathSumEqualtoN_NoRecursion(all_path,rootnode,number):

    if rootnode == None:

        return

    stack = []

    stack.append(rootnode)

    last_pop = rootnode

    while len(stack) != 0:

        curnode = stack[-1]

        #print("stack={}".format([node.val for node in stack])

        if curnode.left == None and curnode.right == None:

            nodeval = [node.val for node in stack]

            if sum(nodeval) == number:

                all_path.append(nodeval)

        if curnode.left != None and curnode.left != last_pop and curnode.right != last_pop:

            stack.append(curnode.left)

        elif curnode.right != None and curnode.right != last_pop and (curnode.left == None or curnode.left == last_pop):

            stack.append(curnode.right)

        else:

            stack.pop()

            last_pop = curnode

    return 

(7)求二叉树中的最大路径和

基本思路:这里的路径被定义为从二叉树中任意节点出发,到达另一个任意节点的所经过节点构成的序列。

先看几个简单的二叉树:

 (1)第一棵树
   1
  / \
 2   3
 (2)第二棵树
   1
  / \
 -2  3
 (3)第三棵树
   1
  / \
 2   -3
 (4)第四棵树
    1
   / \
- 2   -3

接着我们看从节点1出发到其子树能得到的最大路径max_gain(1),
对于第一棵树,max_gain(1) = 1 + 3 = 4;
对于第二棵树,max_gain(1) = 1 + 3 = 4;
对于第三棵树,max_gain(1) = 1 + 2 = 3;
对于第四棵树,max_gain(1) = 1 + 0 = 1;

综上,我们可以得到从一个节点node出发到其子树所能得到的最大路径=从node的左节点出发到其子树所能得到的最大路径、从node的右节点出发到其子树所能得到的最大路径、0,这三者中的最大值加上节点node的值
即:
m a x _ g a i n ( n o d e ) = n o d e . v a l + M a x { m a x _ g a i n ( n o d e . l e f t ) , m a x _ g a i n ( n o d e . r i g h t ) , 0 } max\_gain(node) = node.val + Max\{max\_gain(node.left),max\_gain(node.right),0\} max_gain(node)=node.val+Max{max_gain(node.left)max_gain(node.right)0}

根据上面得出的结论,我们可以计算出每一个二叉树从根节点出发到其子树所能得到的最大路径max_gain(root),但是max_gain(root)不一定是该二叉树的最大路径,比如(1)第一棵树,它的最大路径是是从root的左子树穿过root到达其右子树的一条路径,路径和为max_gain(root.left) + root.val + max_gain(root.right),

所以,在求出从根节点出发到其子树所能得到的最大路径max_gain(root)之后,还需要计算可能的最大路径prob_gain = max_gain(root.left) + root.val + max_gain(root.right),如果prob_gain > max_gain(root),则最大路径和为prob_gain,否则为max_gain(root)。

代码实现

max_path_sum = -float("inf")

def Max_gain(rootnode):

    if rootnode == None:

        return 0

    left_gain = Max_gain(rootnode.left)

    right_gain = Max_gain(rootnode.right)

    root_gain = rootnode.val + max(left_gain,right_gain,0)

    prob_gain = rootnode.val + left_gain + right_gain

    global max_path_sum

    max_path_sum = max(root_gain,prob_gain,max_path_sum)

    return root_gain

参考:leetcode - [二叉树] -(124)二叉树中的最大路径和

(8)二叉树的镜像
基本思路:二叉树的镜像可以通过递归地交换每个节点的左右子树得到,如下图所示:

(1)原二叉树 
   -10
   / \
  9  20
    /  \
   15   7
 (2)镜像
   -10
   / \
 20   9
/  \
7  15

代码实现

def Mirror_Tree(rootnode):

    if rootnode == None:

        return

    Mirror_Tree(rootnode.left)

    Mirror_Tree(rootnode.right)

    temp = rootnode.left

    rootnode.left = rootnode.right

    rootnode.right = temp

    return 

def Mirror_Tree_Nochange(rootnode):

    if rootnode == None:

        return None

    new_rootnode = TreeNode()

    new_rootnode.val = rootnode.val
    
    new_rootnode.right = Mirror_Tree_Nochange(rootnode.left)

    new_rootnode.left = Mirror_Tree_Nochange(rootnode.right)


    return new_rootnode

>>下一篇:通用算法 - [树结构] -二叉树的一些基本操作(三)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 算法9-9~9-12是关于平衡二叉树基本操作,包括插入、删除、旋转等操作。平衡二叉树是一种特殊的叉搜索树,它的左右子树高度差不超过1,可以保证树的高度始终在log(n)级别,从而保证了树的查找、插入、删除等操作的时间复杂度都是O(log(n))。 具体来说,算法9-9是平衡二叉树的插入操作,它首先按照叉搜索树的规则找到要插入的位置,然后通过旋转操作来保持平衡。算法9-10是平衡二叉树的删除操作,它也是通过旋转操作来保持平衡。算法9-11和9-12是平衡二叉树的旋转操作,包括左旋、右旋、左右旋和右左旋,这些操作可以使树重新达到平衡状态。 总之,平衡二叉树基本操作是非常重要的,它们可以保证树的高度始终在log(n)级别,从而保证了树的查找、插入、删除等操作的时间复杂度都是O(log(n)),是一种非常高效的数据结构。 ### 回答2: 平衡二叉树是一种基于叉查找树的数据结构,其在插入和删除节点时会自动调整,以保持树的平衡性。平衡二叉树的常见有AVL树、红黑树等。本文主要介绍平衡二叉树基本操作,包括插入、删除和查找。 9-9 插入操作 在平衡二叉树中插入一个节点的过程和在叉查找树中插入节点的过程类似。不同的是,在插入结束后,需要检查当前节点是否失去平衡并做出相应的调整。 在插入节点时,需要记录节点的高度(从叶节点到根节点的距离)。如果当前节点为空,则将新节点插入到该节点处并将高度设置为1;否则,比较新节点的值和当前节点的值,如果新节点的值小于当前节点的值,则将新节点插入到当前节点的左子树中并更新该节点的高度;如果新节点的值大于当前节点的值,则将新节点插入到当前节点的右子树中并更新该节点的高度。 插入结束后,需要检查当前节点是否失去平衡。我们可以用该节点的左子树高度和右子树高度之差来衡量它是否平衡。如果该节点的平衡因子大于1,则需要进行旋转操作,以恢复平衡。 9-10 删除操作 在平衡二叉树中删除一个节点需要分为以下三种情况: 1. 被删除的节点为叶子节点,直接删除即可。 2. 被删除的节点有一个子节点,将该子节点代替被删除的节点即可。 3. 被删除的节点有两个子节点,需要找到它的中序遍历下一个节点(即比它大的最小节点)代替被删除的节点。如果该节点有右子树,则中序遍历下一个节点为右子树中最小的节点;如果该节点没有右子树,则中序遍历下一个节点为它的某个祖先节点。 删除结束后,需要检查当前节点是否失去平衡。如果失去平衡,则需要进行旋转操作,以恢复平衡。 9-11 查找操作 在平衡二叉树中查找一个节点的过程和在叉查找树中查找节点的过程类似。需要从根节点开始,比较查找的值和当前节点的值。如果查找的值小于当前节点的值,则在左子树中递归查找;如果查找的值大于当前节点的值,则在右子树中递归查找;如果查找的值等于当前节点的值,则返回该节点。 9-12 平衡因子 平衡二叉树的平衡因子定义为当前节点的左子树高度和右子树高度之差。如果平衡因子的绝对值大于1,则说明该节点失去了平衡。在平衡二叉树中,每个节点的平衡因子只能为-1、0或1。如果不是这三个值,则需要进行旋转操作以恢复平衡。 ### 回答3: 平衡二叉树是一种特殊的叉搜索树,它的左右子树高度差不超过1。由于平衡二叉树对于插入、删除、查找等操作的时间复杂度都是O(logn),因此在许多应用场景中得到了广泛的应用。平衡二叉树基本操作包括插入、删除、查找等,以下分别介绍: 9-9 插入操作:平衡二叉树的插入操作与普通叉搜索树相同,只是插入后需要进行平衡处理,避免出现左右子树高度差不平衡的情况。例如插入节点x,需要先查找其应当插入的位置,然后通过旋转操作将其父节点与祖父节点一起旋转,使得树重新平衡。插入操作的时间复杂度为O(logn)。 9-10 删除操作:删除操作也类似于普通叉搜索树,需要删除节点x后通过旋转操作迭代处理其祖先节点的平衡性,保证整个树的平衡性。删除操作的时间复杂度为O(logn)。 9-11 查找操作:查找操作与普通叉搜索树相同,只是由于平衡二叉树的高度比较平衡,因此可以保证其查找效率较高。查找操作的时间复杂度为O(logn)。 9-12 平衡操作:平衡二叉树的平衡操作主要包括旋转操作和重构操作。旋转操作通过将子树旋转到左右子树高度相等来实现平衡,分为左旋和右旋两种。重构操作通过重新构建平衡二叉树的结构来保证整个树的平衡性。平衡操作的时间复杂度为O(1)。 综上所述,平衡二叉树是一种高效的数据结构,其插入、删除、查找等基本操作时间复杂度都为O(logn),通过平衡操作可以保证整个树的平衡性。在实际应用中,平衡二叉树被广泛应用于数据库、搜索引擎、红黑树等场景中,具有重要的实用价值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Albert_YuHan

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值