python算法学习——二叉树

二叉树的结构

二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
python算法学习——二叉树_第1张图片
二叉树的特殊类型:
满二叉树:
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树;
完全二叉树:
深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树。
二叉搜索树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛。
python中二叉树节点定义如下,含有值val,左节点left,右节点right:

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

LeetCode上的二叉树表题目分享

二叉树主要是通过遍历实现的,一般需要用到递归或者迭代,对于递归,需要注意其需要递归之处及终止条件。

1. 剑指offer 25 二叉树的深度

重点在于递归原理是,主树的深度为其左右子树中最大值加一,因此根据此原理构建处maxDepth函数。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root: return 0 
        else: return max(self.maxDepth(root.right),self.maxDepth(root.left))+1

2. 剑指offer 55 平衡二叉树

解法一:常规方法,先计算出左右子树的深度再进行比较。
不过这样会导致很多节点的重复计算

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def depth(root:TreeNode):	# 计算深度
            if not root: return 0
            return max(depth(root.right),depth(root.left))+1
        # 判断,因为要判断左右子树本身是否为平衡树
        if not root: return True
        if abs(depth(root.left)-depth(root.right)) <=1 and self.isBalanced(root.left) and self.isBalanced(root.right): return True
        return False

解法二:
参考解法
优化的地方在于,事先判断左子树和右子树是否为平衡树,如果不是则直接退出(返回-1),达到了剪枝的目的。

        # 方法二
        def find(root:TreeNode):
            if not root: return 0
            left = find(root.left)  # -1
            if left == -1:return -1
            right = find(root.right)    # -1
            if right == -1:return -1
            return max(left,right)+1 if abs(left-right)<=1 else -1
        return find(root) != -1

3. 剑指offer 28 对称二叉树

重点在于迭代的停止条件,
若两边节点为空,则返回True,若有一个为空,或者均不为空,但值不同,则返回False;
迭代部分是对左子树的左节点和右子树的右节点,以及左子树的右节点和右子树的左节点进行比较。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def duicheng(R,L):
            # 两边同时为空
            if not L and not R: return True
            # 若有一个为空,或者都不为空,但值不同
            if not L or not R or (L.val != R.val): return False
            return duicheng(R.left,L.right) and duicheng(L.left,R.right)
        return duicheng(root.right,root.left) if root else True

4. 剑指offer 27 二叉树的镜像

重点在于交换一个节点的左右子节点。

# 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
        # temp = (root.left)
        root.left,root.right = self.mirrorTree(root.right),self.mirrorTree(root.left)
        return root

5. 剑指offer 68-1 二叉树最近的公共祖先

这个在于判断条件比较复杂,

  1. 如果节点本身为q或者p,则返回节点;
  2. 如果节点既不在左也不在右,则说明没有;
  3. 如果节点在左,则说明在左边节点,如果节点在右,说明在右边节点;
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root==q or root==p:return root
        left = self.lowestCommonAncestor(root.left,p,q)
        right = self.lowestCommonAncestor(root.right,p,q)
        if not left and not right:return None   # 均为空
        if left and not right: return left  # 左有,
        if right and not left: return right # 右有
        return root 

6. 剑指offer 68-2 搜索二叉树最近的公共祖先

上面一题是针对的普通的二叉树,这题是搜索二叉树,搜索二叉树的特点就是左节点小于根节点小于右节点(因此中序遍历是从小到大的遍历),可以因此简化判断条件。。
方法一:通用的递归方法

class Solution:
	def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
	    
        if root.val>p.val and root.val>q.val:	# 说明在左子树
            return self.lowestCommonAncestor(root.left,p,q)
        elif root.val<p.val and root.val<q.val:	# 说明在右子树
            return self.lowestCommonAncestor(root.right,p,q)
        return root

方法二:迭代方法
先保证p、q的大小确定,则可以简化判断条件

        if p.val>q.val: p,q=q,p     # p
        while root:
            if root.val<p.val:      # 说明在右子树
                root = root.right
            if root.val>q.val:      # 说明在左子树
                root = root.left
            else:                   # 说明在两侧,即root为公共节点
                return root

7. 剑指offer 54 二叉搜索树的第k大节点

二叉搜索树的特点是,节点值大小按左,中,右排列,因此,中序遍历的话,则可以得到顺序的节点值,因此,本题适合用中序遍历做。

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        ls = []
        def medium(root):
            if not root: return None
            if root.right: medium(root.right)
            if root.val: ls.append(root.val)
            if root.left: medium(root.left)
            # return root
        medium(root)
        return ls[k]

改进:

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        self.m = k
        def medium(root):
            if not root: return None
            medium(root.right)
            if self.m==0:
                return None
            self.m -= 1
            if self.m==0: self.res = root.val
            medium(root.left)
        medium(root)
        return self.res

8. 剑指offer 32-1 从上到下打印二叉树

明显的,考察的是二叉树的层序遍历。。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        if not root: return []
        queue = [root]
        self.val = []
        while queue:
            vertex = queue.pop(0)   # 首节点出列
            self.val.append(vertex.val)
            if vertex.left:     queue.append(vertex.left)
            if vertex.right:    queue.append(vertex.right)
        return self.val

9. 剑指offer 32-2 从上到下打印二叉树

这个是在上一题的基础上,加上一个条件:按每一层打印。因此在层序遍历的基础上,分开每一层。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root: return []
        queue = [root]
        self.ls = []
        while queue:
            tmp = []
            for _ in range(len(queue)): # 每一层有多少节点就循环多少次
                vertex = queue.pop(0)	# 正好循环完所有节点都存在tmp里面
                tmp.append(vertex.val)
                if vertex.left:
                    queue.append(vertex.left)
                if vertex.right:
                    queue.append(vertex.right)
            self.ls.append(tmp)			# 保存每一层的输出
        return self.ls

10. 剑指offer 32-3 从上到下打印二叉树

本题也是在上一题的基础上,隔一层换一次输出方向,因此需要控制每一层节点放进队列的前后顺序,因此采用collections模块的deque函数,优点是方便对队列两端的操作(其实上面两题也可以用它)。

import collections
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root: return []
        queue = collections.deque()		# 创建队列
        queue.append(root)				# append和list的append一样,从后面添加
        self.ls = []
        while queue:
            tmp = collections.deque()
            for i in range(len(queue)):
                vertex = queue.popleft()	# 从前面弹出(即首节点)
                if len(self.ls)%2:			# 如果为偶数层,从队列尾部添加
                    tmp.append(vertex.val)
                else:   					# 奇数层,从队列前面添加,非常方便(这样就倒序了)
                	tmp.appendleft(vertex.val)
                if vertex.right:
                    queue.append(vertex.right)
                if vertex.left:
                    queue.append(vertex.left)
            self.ls.append(list(tmp))		# 注意这里需要转化一下。。。
        return self.ls

11. 剑指offer 7 重建二叉树

根据一位大佬的代码总算弄清楚了原理。其思路是,前序遍历中的子树序列仍为前序遍历结果(显而易见,因为都是递归套娃而来),中序遍历的子树序列仍为中序遍历。因此,根据根节点的位置,划分出左子树序列和右子树序列,然后以左子树前序序列和中序序列为函数的输入,返回根节点的左子树,同理返回右子树,再将这两棵子树加到根节点的左右,程序最终返回完整的root。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder or not inorder:
            return None
        if len(preorder)!=len(inorder):
            return None
        root_node = TreeNode(preorder[0])
        root_pos = inorder.index(root_node.val)

        pre_left_node = preorder[1:root_pos+1]
        pre_right_node = preorder[root_pos+1:]

        in_left_node = inorder[:root_pos]
        in_right_node = inorder[root_pos+1:]
        left = self.buildTree(pre_left_node, in_left_node)
        right = self.buildTree(pre_right_node, in_right_node)
        # 接上root
        root_node.left = (left)     # 因为返回值已经是node了
        root_node.right = (right)   # 这个也是
        return root_node

12. 剑指offer 33 二叉搜索树的后序遍历序列

二叉搜索树的特点是左节点<根节点<右节点,后序遍历的顺序是左右根,因此需要先将序列分成根节点、左子树、右子树。
方法一:通过遍历找出左子树和右子树的分界点mid,然后由于左子树由于前面步骤,确保了左子树必定小于根节点,只需要判断右子树是否都大于根节点了。

class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        def search(left,root):
            if left>=root:   return True
            tmp = left
            while postorder[tmp]<postorder[root]:   tmp+=1
            mid = tmp
            while postorder[root]<postorder[mid]:   mid+=1
            return mid==root and search(0,mid-1) and search(mid,root)
        return search(0,len(postorder)-1)

方法二:思路和方法一一致,但实现更为清晰,更容易想到。

class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        if not postorder: return True
        root = postorder[-1]
        length = len(postorder)
        posi = 0
        for i in range(length):
            if postorder[i]>=root:
                posi = i
                break
        left = postorder[0:posi]
        right = postorder[posi:-1]
        for val in right:
            if val<root:
                return False
        return self.verifyPostorder(left) and self.verifyPostorder(right)

13. 剑指offer 26 树的子结构

参考来源
本题原理是匹配树节点,利用前序遍历的过程中,搜索匹配B的根节点,主要是终止条件比较复杂,不好分析。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        def recur(A,B):
            if not B: return True
            if not A or A.val != B.val: return False
            return  recur(A.left,B.left) and recur(A.right,B.right)
        return bool(A and B) and (self.isSubStructure(A.left,B) or self.isSubStructure(A.right,B) or recur(A,B) )

14. 剑指offer 36. 二叉搜索树与双向链表

根据二叉搜索树的特点,明显需要进行中序遍历使其有序,通常的想法是,先进行中序遍历,将节点按顺序存入列表,再进行一个循环,改变节点的左右节点的指向,需要注意对头节点和尾节点的处理。。

"""
# Definition for a Node.
class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
"""
class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        # 中序遍历
        if not root: return None
        if not root.left and not root.right:
            root.left = root
            root.right = root
            return root
        ls = []
        def recur(root):
            if root.left:
                recur(root.left)
            ls.append(root)
            if root.right:
                recur(root.right)
        
        recur(root)
        # 节点处理
        ls[0].left = ls[-1]
        ls[0].right = ls[1]
        ls[-1].left = ls[-2]
        ls[-1].right = ls[0]
        for i in range(1,len(ls)-1):
            ls[i].left = ls[i-1]
            ls[i].right = ls[i+1]
        return ls[0]

第二种方法是直接在中序遍历的时候直接对节点进行处理。。

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        def dfs(cur):
            if not cur: return
            dfs(cur.left) # 递归左子树
            if self.pre: # 修改节点引用
                self.pre.right, cur.left = cur, self.pre
            else: # 记录头节点
                self.head = cur
            self.pre = cur # 保存 cur
            dfs(cur.right) # 递归右子树
        
        if not root: return
        self.pre = None
        dfs(root)
        self.head.left, self.pre.right = self.pre, self.head
        return self.head

15. 剑指offer 37 序列化二叉树

根据给出的示例,可以看出这个主要框架是层序遍历,因此需要采用BFS法,需要注意的是,叶子节点也需要输出其左右节点(null)。

参考解法

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
import collections
class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        if not root: return "[]"
        queue = collections.deque()
        queue.append(root)
        res = []
        while queue:
            vertex = queue.popleft()
            if vertex:
                res.append(str(vertex.val))     # 因为要求是字符串类型
                queue.append(vertex.left)
                queue.append(vertex.right)
            else:
                res.append("null")
        return "["+",".join(res)+"]"

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        if data=="[]": return None
        res,i = data[1:-1].split(','),1
        root = TreeNode(int(res[0]))
        queue = collections.deque()
        queue.append(root)

        while queue:
            vertex = queue.popleft()
            if res[i] != "null":
                vertex.left = TreeNode(int(res[i]))
                queue.append(vertex.left)
            i += 1
            if res[i] != "null":
                vertex.right = TreeNode(int(res[i]))
                queue.append(vertex.right)
            i += 1
        return root

总结

剑指offer的15道二叉树题这样总算是刷完了,这样记录下来,短期内还是印象还算是比较深刻的,也算是从一开始一团雾水到逐渐清晰的过程,尽管大部分的题目还是得看大佬们(主要是Krahets)的解析,但比开始之前好多了。。。

  1. 二叉搜索树比较有特点,中序遍历是从小到大;
  2. 二叉树的遍历方式有前序遍历(根左右),中序遍历(左根右),后序遍历(左右根)以及层序遍历(从上到下,从左到右);
  3. 前序、中序、后序都需要用到递归,在程序上的区别,主要是recur(递归函数)中,判断条件放的位置,如下所示:
def recur(root):
    if not root: return
    dfs(root.left)  # 左
    print(root.val) # 根
    dfs(root.right) # 右
# 左根右,中序遍历
  1. 其中层序遍历一般是采用BFS(广度优先算法),需要用到队列(collections模块的deque函数);
  2. DFS(深度优先算法)需要用到栈(从程序上与BFS相比,感觉像是popright()和popleft()的差别);

你可能感兴趣的:(算法刷题记录,二叉树,数据结构,算法,python,数据库)