signed

QiShunwang

“诚信为本、客户至上”

Python数据结构和算法笔记九:树

2020/8/20 12:49:32   来源:

文章目录

        • 树的概念
        • 树的分类
        • 代码表示二叉树
        • 二叉树的三种遍历顺序
        • 二叉树练习
          • 二叉树的后序遍历
          • 二叉树的层序遍历
          • 翻转二叉树
          • 二叉树的最大深度
          • 从前序与中序遍历序列构造二叉树
        • 二叉树总结

  • 一种包括节点(nodes)和边(edges)的拥有层级关系的结构
  • 树的形式和家谱非常类似

树的概念

  • 1、根节点(root):树的最上层的节点,任何非空的树都有一个节点
  • 2、路径(path):从起始节点到终止节点经历过的边
  • 3、父亲(parent):除了根节点,每个节点的上一层连接的节点就是它的父亲
  • 4、孩子(children):每个节点由边指向的下一层节点
  • 5、兄弟(siblings):同一个父亲并且处于同一层的节点
  • 6、子树(subtree):每个节点包含它所有的后代组成的子树
  • 7、叶子节点(leaf node):没有孩子的节点称为叶子节点

树的分类

  • 这里只介绍二叉树
  • 二叉树:每个节点最多只有两个孩子,每个节点拥有的孩子节点范围:0~2
    • 满二叉树:每个内部节点(非叶子节点)都包含两个还是,即每个节点要么没有孩子节点,要么有两个
    • 完美二叉树:所有的叶子节点都在同一层
    • 完全二叉树:在完美二叉树的基础上,添加多一层或者不添加,使最下层的节点是无间隙的从左到右填充

代码表示二叉树

  • data表示节点数据,left表示左孩子指针,right表示右孩子指针

  • class TreeNode:
    	def __init__(self, data, left=None, right=None):
    		self.data = data
    		self.left = left
    		self.right = right
    

二叉树的三种遍历顺序

  • 先(根)序遍历

  • 中(根)序遍历

  • 后(根)序遍历

  • 深度遍历:递归与非递归方式,使用栈模拟递归过程,实现非递归遍历,因为递归操作本身是因为计算机帮我们完成了入栈和出栈过程

  • 广度遍历(层序遍历):使用list或者队列实现

  • 递归,非递归(使用栈,因为递归操作是),层序遍历(list或队列)三种方式

  • 递归方式

    • class TreeNode():
          def __init__(self, data, left=None, right=None):
              self.data = data
              self.left = left
              self.right = right
      
      
      def preorder(root):
          if root is not None:
              print(root.data,end=' ')
              preorder(root.left)
              preorder(root.right)
      def inorder(root):
          if root is not None:
              preorder(root.left)
              print(root.data)
              preorder(root.right)
      
      def postorder(root):
          if root is not None:
              preorder(root.left)
              preorder(root.right)
              print(root.data)
      
  • 非递归方式(使用栈)(标记法)

    • from collections import deque
      def preorder_use_stack(root):
          if not root:
              return
          s = deque()
          s.append((root, False))
          while s:
              node, visted = s.pop()
              if node:
                  if visted:
                       print(node.data)
                  else: # stack是 后进先出。所以先序:根左右出栈 -> 右左根入栈,同理中序和后续
                      s.append((node.right, False))
                      s.append((node.left, False))
                      s.append((node, True))
      
  • 层序遍历(使用list/队列)

    • # 使用list完成层序遍历
      def layer_order_uselist(root):
        # if not root:
          #     return
          # cur_nodes = [root]
          # next_nodes = []
          # while cur_nodes or next_nodes:
          #     print(cur_nodes)
          #     for node in cur_nodes:
          #         if node.left:
          #             next_nodes.append(node.left)
          #         if node.right:
          #             next_nodes.append(node.right)
          #         cur_nodes = next_nodes
          #         next_nodes = []
      
          li = [root]
          while li:
              n = len(li)
              for _ in range(n):
                  l = li.pop(0)
                  if l:
                      print(l.data)
                      li.append(l.left if l.left else None)
                      li.append(l.right if l.right else None)
      
      # 使用队列完成层序遍历
      def layer_order_usequque(root):
          d = deque()
          d.append(root)
          while d:
              node = d.popleft()
              if node:
                  print(node.data)
                  d.append(node.left if node.left else None)
                  d.append(node.right if node.right else None)
      

二叉树练习

二叉树的后序遍历
  • 思路:

    • 1、使用迭代遍历方法实现(栈+标记法)

    • 2、使用递归实现

    • 给定一个二叉树,返回它的 后序 遍历。
      
      示例:
      
      输入: [1,null,2,3]  
         1
          \
           2
          /
         3 
      
      输出: [3,2,1]
      
      class TreeNode:
          def __init__(self, x, left=None, right=None):
              self.val = x
              self.left = left
              self.right = right
      
      # # 递归,后序遍历:左右根
      # class Solution:
      #     def postorder(self, root, res):
      #         if not root:
      #             return
      #         self.postorder(root.left,res)
      #         self.postorder(root.right,res)
      #         print(root.val)
      #         res.append(root.val)
      
      #     # 后序遍历:左右根
      #     def postorderTraversal(self, root: TreeNode):
      #         res = []
      #         self.postorder(root, res)
      #         return res
      
      
      # 迭代后序遍历
      class Solution:
      
          def postorderTraversal(self, root: TreeNode):
              stack = deque()
              res = []
              stack.append((root, False))
              while stack:
                  node, visited = stack.pop()
                  if node:
                      if visited:
                          res.append(node.val)
                      else:  # 由于是用栈存储,所以后序遍历原本时左右根->变成入栈顺序根右左
                          stack.append((node, True))
                          stack.append((node.right, False))
                          stack.append((node.left, False))
              return res
      
二叉树的层序遍历
  • 思路:

    • 使用list/队列

    • 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
      
      示例:
      二叉树:[3,9,20,null,null,15,7],
      
          3
         / \
        9  20
          /  \
         15   7
      
      返回其层次遍历结果:
      [
        [3],
        [9,20],
        [15,7]
      ]
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def levelOrder(self, root: TreeNode) -> List[List[int]]:
              if not root:
                  return []
              cur_nodes = [root]
              next_nodes = []
              res = []
              vals = [i.val for i in cur_nodes]
              res.append(vals)
              while cur_nodes or next_nodes:
                  for node in cur_nodes:
                      if node.left:
                          next_nodes.append(node.left)
                      if node.right:
                          next_nodes.append(node.right)
                  if next_nodes:
                      res.append([i.val for i in next_nodes])
                  cur_nodes = next_nodes
                  next_nodes = []
              return res
      
翻转二叉树
  • 思路

    • 递归:递归出口、子问题、返回值

    • 翻转一棵二叉树。
      示例:
      
      输入:
           4
         /   \
        2     7
       / \   / \
      1   3 6   9
      
      输出:
           4
         /   \
        7     2
       / \   / \
      9   6 3   1
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def invertTree(self, root: TreeNode) -> TreeNode:
              if not root:
                  return
              root.left, root.right = root.right, root.left 
              self.invertTree(root.left)
              self.invertTree(root.right)
              return root
      
二叉树的最大深度
  • 思路:

    • 递归:递归出口、子问题、返回值

    • 给定一个二叉树,找出其最大深度。
      
      二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
      
      说明: 叶子节点是指没有子节点的节点。
      
      示例:
      给定二叉树 [3,9,20,null,null,15,7],
          3
         / \
        9  20
          /  \
         15   7
       
      返回它的最大深度 3 。
      
    • # 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
              left = self.maxDepth(root.left) + 1
              right = self.maxDepth(root.right) + 1
              return max(left, right) 
      
从前序与中序遍历序列构造二叉树
  • 思路:

    • 1、递归出口:序列为空的时候

    • 2、子问题:递归地去构造左右子树

      • 从先序中找到根节点
      • 从中序中判断根节点位置,从而判断左右子树的长度
      • 根据中序中得到的左右子树的长度可以回到先序中可以找出对应的左右子树
      • 递归地去构造左右子树
    • 3、返回值:根节点

    • 根据一棵树的前序遍历与中序遍历构造二叉树。
      
      注意:
      你可以假设树中没有重复的元素。
      
      例如,给出
      前序遍历 preorder = [3,9,20,15,7]
      中序遍历 inorder = [9,3,15,20,7]
      
      返回如下的二叉树:
      
          3
         / \
        9  20
          /  \
         15   7
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def buildTree(self, preorder, inorder):
              if not preorder:
                  return None
              root_val = preorder[0]
              root = TreeNode(root_val)  # build root node
              root_idx = inorder.index(root_val)
              # 计算左右子树的长度
              left_len = root_idx
              right_len = len(inorder)-root_idx-1
              # 递归构造
              if left_len:
                  root.left = self.buildTree(preorder[1:left_len+1], inorder[0:root_idx])
              if right_len:
                  root.right = self.buildTree(preorder[left_len+1:], inorder[root_idx+1:])
      
              return root
      

二叉树总结

  • 二叉树是递归定义的,所以很多问题可以用递归解决
  • 掌握二叉树的遍历方式可以解决很多基础问题
  • 基于二叉树还可以衍生出二叉搜索树和链表的一些问题,可以去leetcode找些题目练手