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

1、前言

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

2、二叉树的基本操作

(1)二叉树的递归遍历
递归的形式非常简洁,几行代码即可搞定。前序、后序、中序遍历的形式基本一样,只是输出结点值的时机不一样。
基本思想:如果该结点为叶结点,则直接返回,否则,看该结点的左孩子是否为空,如果不为空,则递归的遍历其左孩子;接着看其右孩子是否为空,如果不为空,则递归的遍历其右孩子;
代码实现

    
def PreOrder_recursion(rootnode):

    if rootnode == None:

        return

    print(rootnode.val)

    if rootnode.left != None:

        PreOrder_recursion(rootnode.left)

    if rootnode.right != None:

        PreOrder_recursion(rootnode.right)

    return

def MidOrder_recursion(rootnode):

    if rootnode == None:

        return

    if rootnode.left != None:

        MidOrder_recursion(rootnode.left)

    print(rootnode.val)

    if rootnode.right != None:

        MidOrder_recursion(rootnode.right)

    return 

def BackOrder_recursion(rootnode):

    if rootnode == None:

        return 
    
    if rootnode.left != None:

        BackOrder_recursion(rootnode.left)

    if rootnode.right != None:

        BackOrder_recursion(rootnode.right)

    print(rootnode.val)

    return

(2)二叉树的非递归遍历

基本思想:如果采用非递归遍历,我们需要使用一个栈来模拟函数的递归调用。对于三种遍历方式,我们都采用push当前结点->push左子树->pop左子树->push右子树->pop右子树的方式,只是不同的遍历方式,输出结点值的时机不同。

对于前序遍历来说,每次访问到一个节点,即每次要将节点进栈时,输出该节点的值;【即在左右节点入栈时输出左右子节点的值】

对于中序遍历来说,每次将当前节点的右节点进栈时,输出当前节点的值;【即在右节点进栈、以及pop叶节点的时候,输出当前节点的值】

对于后序遍历来说,每次将节点从栈中弹出的时候,输出当前节点的值。【即在pop的时候输出当前节点的值】

另外,我们还需要一个last_pop指针来记录上次pop的节点;

如果当前节点的左节点非空,并且左节点和子节点都不是上次last_pop,则将左节点进栈;

如果当前节点的右节点非空,也不是last_pop,且当前节点的左节点为空或者左节点是last_pop,则将右节点进栈;

否则的话,让当前节点出栈,让当前节点标记为last_pop。

  • 注意,这里有两个比较重要的数据结构:
  • 当前节点:栈顶元素;
  • last_pop:上一次pop出去的结点;

当前结点被pop有两种情况,一是当前结点为叶节点(左节点 和右节点都为空),二是当前结点的左右结点都已经被访问过了(右节点为last_pop)

二叉树的前序、中序、后序遍历采用的都是深度优先策略。

代码实现

def PreOrder_Norecursion(rootnode):
    stack = []

    stack.append(rootnode)

    print(rootnode.val,end="")

    last_pop = rootnode

    while len(stack) != 0:

        curnode = stack[-1]

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

            stack.append(curnode.left)

            print(curnode.left.val,end="")

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

            stack.append(curnode.right)

            print(curnode.right.val,end="")

        else:

            stack.pop()

            last_pop = curnode

    return


def MidOrder_Norecursion(rootnode):
    stack = []

    stack.append(rootnode)

    last_pop = rootnode

    while len(stack) != 0:

        curnode = stack[-1]

        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)

            print(curnode.val,end="")

        else:

            stack.pop()

            last_pop = curnode

            if curnode.right == None:
                print(curnode.val,end="")

    return


def BackOrder_Norecursion(rootnode):
    stack = []

    stack.append(rootnode)

    last_pop = rootnode

    while len(stack) != 0:

        curnode = stack[-1]

        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

            print(curnode.val,end="")
    return

(3)二叉树种叶子节点的个数
基本思路:使用变量leaf_count对叶节点进行计数,在遍历二叉树时,如果遇到叶节点,则leaf_count 的值加1。

代码实现

def LeafNums_Norecursion(rootnode):
    leaf_count = 0

    stack = []

    stack.append(rootnode)

    last_pop = rootnode

    while len(stack) != 0:

        curnode = stack[-1]

        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

            if curnode.left == None and curnode.right == None:
                leaf_count += 1

    return leaf_count

leaf_count2 = 0
def LeafNums_Recursion(rootnode):
    if rootnode == None:
        return

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

        global leaf_count2

        leaf_count2 += 1

    if rootnode.left != None:
        LeafNums_Recursion(rootnode.left)

    if rootnode.right != None:
        LeafNums_Recursion(rootnode.right)

    return

(4)二叉树的深度
基本思想
(1)递归:如果节点为空,则深度为0,否则,该树的深度等于左子树的深度与右子树深度的最大值+1
(2)非递归:stack模拟函数的递归调用,那么栈中元素的最大数量即为树的深度。

代码实现

def Maxdepth_Recursion1(rootnode):
    # 遇到空节点才返回

    if rootnode == None:
        return 0

    left_depth = Maxdepth_Recursion1(rootnode.left)

    right_depth = Maxdepth_Recursion1(rootnode.right)

    root_depth = max(left_depth, right_depth) + 1

    return root_depth


def Maxdepth_Recursion2(rootnode):
    # 遇到叶节点或者空节点,返回,避免多递归一层。

    if rootnode == None:
        return 0

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

    left_depth = 0

    if rootnode.left != None:
        left_depth = Maxdepth_Recursion2(rootnode.left)

    right_node = 0

    if rootnode.right != None:
        right_depth = Maxdepth_Recursion2(rootnode.right)

    root_depth = 0

    root_depth = max(left_depth, right_depth) + 1

    return root_depth


def Maxdepth_NoRecursion(rootnode):

    stack = []

    stack.append(rootnode)

    last_pop = rootnode

    depth = 0

    while len(stack) != 0:

        curnode = stack[-1]

        depth = max(len(stack), depth)

        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 depth

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

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

余额充值