zoukankan      html  css  js  c++  java
  • 数据结构与算法习题总结——树结构

    本篇为在leetcode和牛客等平台刷题过程中遇到的典型例题及自己的学习笔记,会随着学习进程不断更新,题目都借鉴自网络或书籍,仅用作个人学习。由于水平实在有限,不免产生谬误,欢迎读者多多批评指正。如需要转载请与博主联系,谢谢


    二叉树遍历打印

    先序遍历(前序遍历):对树中任意子树(或整棵树),先打印根节点,然后先序遍历其左子树,再先序遍历其右子树。
    中序遍历:对树中任意子树(或整棵树),先中序遍历根节点的左子树,然后打印根节点,再中序遍历其右子树。
    后序遍历:对树中任意子树(或整棵树),先后序遍历根节点的左子树,然后后序遍历根节点的右子树,最后打印根节点。

    '''
    class BinaryTree:
        def __init__(self, data):
            self.data = data
            self.left = None
            self.right = None
    '''
    def preorderTraversal(node):      
        # 先序遍历(递归方法,中序与后序的递归与此很类似,就是打印根节点和递归子树的顺序不同)
        if not node:
            return 
        print(node.data)
        preorderTraversal(now.left)
        preorderTraversal(now.right)
    
    def preorderTraversal(node):      
        # 先序遍历(循环方法,核心思想就是先打印当前根节点,然后向左遍历,并用栈储存尚未遍历右子树的节点)
        if not node:
            return
        stack = []
        while node or stack:
            while node:
            # 当node为None时说明上一节点已无左子树,可以从栈顶取一个节点来遍历右子树
                print(node.data)
                stack.append(node)
                node = node.left
            node = stack.pop()
            node=node.right
    
    def inorderTraversal(node):      
        # 中序遍历(循环方法,与前序类似,只不过打印根节点要放在节点从栈顶弹出之后,遍历右子树之前)
        if node == None:
            return
        stack = []
        while node or stack:
            while node:
                stack.append(node)
                Node = node.left
            node = stack.pop()
            print(node.data)
            node=node.right
    
    def postorderTraversal(node):      
        # 后序遍历(循环方法)
        if node == None:
            return
        stack = []
        while node or stack:
            while node:
                # 优先让左节点进栈,左子树为空则右节点进栈
                stack.append(node)
                node = node.left if node.left else node.right
            node = stack.pop() 
            print(node.data)
            #若当前节点是栈顶节点的左节点,则去访问栈顶节点的右节点
            if len(stack)!=0 and stack[-1].left==node:
                node=stack[-1].right
            else:
                #没有右结点或右子树遍历完毕,该结点出栈
                node=None
    
    

    二叉树的序列化与反序列化

    序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中。反序列化是指将前述连续的数据序列通过网络传输到另一个计算机环境后,采取相应的逆操作重构得到原数据结构。

    '''
    Definition for a binary tree node.
    class TreeNode(object):
        def __init__(self, x):
            self.val = x
            self.left = None
            self.right = None
    '''
    class Codec:
        # 以下为层序遍历的方法实现序列化与反序列化,核心思想就是利用队列实现对节点的层序存储和读取。
        def serialize(self, root):
            quene = []
            nodeList = []
            if not root:
                return nodeList
            quene.append(root)
            while quene:   # 每从队列头部获取一个节点,就将该节点不为空的左右子节点存入队列的尾部,直至队列为空。
                nextNode = quene.pop(0)
                if not nextNode:
                    nodeList.append('null') 
                else:
                    nodeList.append(nextNode.val)
                    if nextNode.left != None:  
                        quene.append(nextNode.left)
                    else:
                        quene.append(None)
                    if nextNode.right != None:
                        quene.append(nextNode.right)
                    else:
                        quene.append(None)
            return nodeList    # 返回层序遍历后得到的序列
    
        def deserialize(self, nodeList):
            """Decodes your encoded nodeList to tree.
            :type data: str
            :rtype: TreeNode
            """
            if not nodeList:
                return None
            root = TreeNode(nodeList[0])
            Nodes = [root]   # Nodes队列中会将data中的null转换为空节点None
            j = 1  # 记录当前所用data中的节点数
            for node in Nodes: # 子节点加入列表的尾部,以保证优先处理完本层所有节点后再处理下一层节点
                if node != None:
                    node.left = (TreeNode(nodeList[j]) if nodeList[j] != 'null' else None)
                    Nodes.append(node.left)
                    j += 1
                    if j == len(nodeList): # 判断是否队列中所有节点已用完
                        return root
                    node.right = (TreeNode(nodeList[j]) if nodeList[j] != 'null' else None)
                    j += 1
                    Nodes.append(node.right)
                    if j == len(nodeList):
                        return root
    

    二叉树深度问题

    给定一个二叉树,找出其最大深度。如给定二叉树 [3,9,20,null,null,15,7](层序遍历),返回它的最大深度3。

    class Solution:
        def maxDepth(self, root):
            # 方法一:类似层序遍历的方法,用两个列表分别记录当前层及下一层的节点并记录层数
            if not root:
                return 0
            layer1 = [root]
            layer2 = []
            answer = 0
            # 首先确定根节点为第一层,它产生的子节点为第二层,每次第一层消耗完后,将第二层节点换给第一层,依次类推。
            while layer1:
                node = layer1.pop(0)
                if node.left != None:
                    layer2.append(node.left)
                if node.right != None:
                    layer2.append(node.right)
                if layer1 == []:
                    layer1 = layer2
                    layer2 = []
                    answer += 1
            return answer
        return maxDepth(root)
    
        def maxDepth2(node):
            # 方法二:递归——思路就是子树的深度等于当前根节点的左右子树中较大的深度+1
            return 0 if node == None else max(maxDepth2(node.left),maxDepth2(node.right))+1
        return maxDepth2(root)
    

    上述两种思路分别对应广度优先搜索(bfs)和深度有限搜索(dfs),前者利用队列实现,后者利用栈(或递归中的函数栈)实现。bfs可以认为是每次只对当前节点下一层
    可达的目标进行遍历,然后回溯到当前节点的同层节点,并对其下一层可达的目标进行遍历,在搜索空间中类似石头激起的水波扩散;而dfs则是在当前目标下选定一个方向走到底,然后回溯到起点后再换方向走到底,类似小老鼠探索迷宫的过程。

    利用前序遍历和中序遍历结果重建二叉树

    根据一棵树的前序遍历与中序遍历构造二叉树,假设树中没有重复的元素。

    '''
    class BinaryTree:
        def __init__(self, data):
            self.data = data
            self.left = None
            self.right = None
    '''
    class Solution:
        def buildTree(self, preorder: List[int], inorder: List[int]):
            if not inorder:     # 只要中序遍历为空,前序遍历一定也为空
                return None
            root = BinaryTree(preorder[0])      # 前序第一个节点为根节点
            index = inorder.index(root.val)     # 在中序遍历中寻找切分点,利用切分后子树序列长度可推出前序遍历中的两棵子树
            root.left = self.buildTree(preorder[1:index+1],inorder[:index])    # 返回的下一子树的根节点当做现在根节点的左节点,右节点同理
            root.right = self.buildTree(preorder[index+1:],inorder[index+1:])
        
            return root
    

    递归解法的代码很简单,思路就是利用当前子树的前序遍历构建根节点,然后利用当前子树的中序遍历来划分其左子树和右子树部分,递归构建即可。

    二叉树的最近公共祖先

    给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

    '''
    Definition for a binary tree node.
    class TreeNode:
        def __init__(self, x):
            self.val = x
            self.left = None
            self.right = None
    '''
    class Solution:
        '''
        思考可得,迭代时对每棵子树的遍历存在三种成功终止条件(找到最近公共祖先),即当前根节点为两个目标节点之一或两个目标分别在当前根节点的左右子树中;或得到一种失败的终止条件(没有左右子树了)。
        满足终止条件则返回结果,不满足上述四种终止条件则返回下一层迭代的结果。迭代结构有点像员工(解决问题后上报)和领导(自己解决不了问题,将下属的结果统筹后上报)之间的关系。
        这里程序还可以写得再简洁些,但是展开写会比较清楚。
        '''
        def lowestCommonAncestor(self, root, p, q):
            if not root:   # 搜索失败终止条件1
                return None
            if root == p or root == q:   # 搜索成功终止条件1和2
                return root
            # 以上部分在迭代向下搜索的过程中进行
            left = self.lowestCommonAncestor(root.left,p,q)
            right = self.lowestCommonAncestor(root.right,p,q)
            # 以下部分在迭代向上回溯的过程中进行
            if left != None and right != None:   # 搜索成功终止条件3
                return root
            if left == None and right == None:   # 总结下层迭代结果后返回
                return None
            return left if left != None else right
    

    二叉树的翻转

    请完成一个函数,输入一个二叉树,该函数输出它的镜像。(据说不会这个题就进不了谷歌。。)

    '''
    Definition for a binary tree node.
    class TreeNode:
        def __init__(self, x):
            self.val = x
            self.left = None
            self.right = None
    '''
    class Solution:
        # 直接递归将每个节点的左右子树翻转即可
        def mirrorTree(self, root: TreeNode) -> TreeNode:
            if not root: return None
            left = self.mirrorTree(root.right)  # 这里设置两个暂存器是因为如果直接将右子树赋给左节点位置会覆盖原来的左子树,从而使对称的赋值操作无法正确进行
            right = self.mirrorTree(root.left)
            root.left = left
            root.right = right
            return root
    

    平衡二叉树

    给定一个二叉树,判断它是否是高度平衡的二叉树。
    其中一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。

    '''
    这个题目很容易想到的是自顶向下地判断,根节点是否平衡-左子树是否平衡-右子树是否平衡
    其实这样不太好,因为每计算一个子树的高度就要对其拥有的所有节点计算一遍高度,会带来大量重复计算
    而使用从底向上递归的方法复杂度则可以降到O(N),其中只要发现一个节点不平衡就可以通过截断返回-1的方法快速停止递归
    '''
    class Solution:
        def isBalanced(self, root: TreeNode) -> bool:
            return self.heighter(root) != -1
        def heighter(self, root: TreeNode):    
            # 定义一个返回当前树高度的函数供上层节点递归判断使用,若当前子树已经存在不平衡的节点则直接返回-1
            if not root:
                return 0
            l = self.heighter(root.left)
            if l == -1:
                return -1
            heightl = 1 + l
            r = self.heighter(root.right)
            if r == -1:
                return -1
            heightr = 1 + r
            if abs(heightl - heightr) > 1: # 到这里说明其左右子树本身均平衡,需要判断根节点是否平衡
                return -1
            else:
                return max(heightl,heightr)  # 根节点也平衡,向上层返回当前树的高度
    

    参考资料:

    1. https://leetcode-cn.com/ leetcode
    2. https://www.cnblogs.com/bjwu/p/9284534.html 二叉树(前序,中序,后序,层序)遍历递归与循环的python实现
    3. https://kaiwu.lagou.com/course/courseInfo.htm?courseId=185#/content?courseId=185 重学数据结构与算法
    4. https://www.cnblogs.com/tomhawk/p/7464639.html Python --- 二叉树的层序建立与三种遍历
    5. https://zhuanlan.zhihu.com/p/24986203 搜索思想——DFS & BFS(基础篇)
    6. 《剑指offer》 何海涛著
  • 相关阅读:
    test6
    test4
    test3
    20165321 2017-2018-2《Java程序设计》课程总结
    20165321 实验五 网络编程与安全-2
    20165321 实验五 网络编程与安全
    实验四 Android开发基础
    20165321 实验三 敏捷开发与XP实践
    《深入理解计算机系统》第三章 程序的机器级表示学习
    《文献管理与信息分析》第二章
  • 原文地址:https://www.cnblogs.com/liugd-2020/p/13401198.html
Copyright © 2011-2022 走看看