Python 数据结构 - 二叉树

Python 数据结构 - 二叉树

binary tree

1. 基本概念

1.1 例子

Level 0:        A
             /     \
Level 1:    B       C
           / \     / \
Level 2:  D   E   F   G
             /       / \
Level 3:    H       I   J

1.2 相关概念:

  • 根节点(root): 树的最上层的节点,任何非空的树都有一个节点

  • 路径(path): 从起始节点到终止节点经历过的边

  • 父亲(parent):除了根节点,每个节点的上一层边连接的节点就是它的父亲(节点)

  • 孩子(children): 每个节点由边指向的下一层节点

  • 兄弟(siblings): 同一个父亲并且处在同一层的节点

  • 子树(subtree): 每个节点(以该节点为子树的根节点)包含它所有的后代组成的子树

  • 叶子节点(leaf node): 没有孩子的节点成为叶子节点

1.3 属性

  • 节点深度(depth): 节点对应的 level 数字

  • 树的高度(height): 二叉树的高度就是 level 数 + 1,因为 level 从 0 开始计算的

  • 树的宽度(width): 二叉树的宽度指的是包含最多节点的层级的节点数

  • 树的(size):二叉树的节点总个数

一棵 size 为 \(n\) 的二叉树高度最多可以是 \(n\),最小的高度是 \(\lfloor \log_2 n \rfloor + 1\)

1.4 二叉树类型

  • 完美二叉树(perfect binary tree):所有的叶子节点都在同一层,毫无间隙填充了 \(h\) 层。

  • 完全二叉树(complete binary tree):当一个高度为 \(h\) 二叉树,其前 \(h-1\) 高度构成了完美二叉树,并且其最底层的槽被毫无间隙地从左到右填充。

  • 满二叉树(full binary tree):如果每个内部节点(非叶节点)都包含两个 children,就成为满二叉树。

1.5 二叉树遍历

  • 先(根)序遍历: 先处理根,之后是左子树,然后是右子树

  • 中(根)序遍历: 先处理左子树,之后是根,最后是右子树

  • 后(根)序遍历: 先处理左子树,之后是右子树,最后是根

  • 层序遍历:按层遍历,每一层从左到右遍历

2 程序实现

2.1 二叉树的节点

定义一个二叉树的节点类

class BinTreeNode(object):
    def __init__(self, id, left=None, right=None):
        self.id = id
        self.left = left
        self.right = right

2.2 构建二叉树

根据节点 list 构建二叉树

class BinTree(object):
    def __init__(self, root_node=None):
        self.root_node = BinTreeNode(**root_node)
        
        self.node_dict = {}
        node_id = root_node['id']
        # root node
        self.node_dict[node_id] = self.root_node
        return None
    # ---------------------------------------------------------
    def build_from_node_list(self, node_list):
        # 构建 node_dict,存放所有 node 对象
        for node_li in node_list:
            node_id = node_li['id']
            self.node_dict[node_id] = BinTreeNode(**node_li)
        
        # 把每个 node 的 node.left 和 node.right 修改为 node 对象
        # root node
        node_id = self.root_node.id
        node = self.root_node
        node.left = self.node_dict.get(node.left)
        node.right = self.node_dict.get(node.right)

        for node_li in node_list:    
            node_id = node_li['id']
            node = self.node_dict[node_id]
            # 把 node.left 和 node.right 修改为 node 对象
            node.left = self.node_dict.get(node.left)
            node.right = self.node_dict.get(node.right)
            # node_dict.get(key, default=None) 如果查找的 key 不存在时,返回 default
        return None
    # ---------------------------------------------------------

实例

root_node = {'id': 'A', 'left': 'B', 'right': 'C'}
node_list = [
    {'id': 'B', 'left': 'D',  'right': 'E'},
    {'id': 'D', 'left': None, 'right': None},
    {'id': 'E', 'left': 'H',  'right': None},
    {'id': 'C', 'left': 'F',  'right': 'G'},
    {'id': 'H', 'left': None, 'right': None},
    {'id': 'F', 'left': None, 'right': None},
    {'id': 'G', 'left': 'I',  'right': 'J'},
    {'id': 'I'}, # 如果不存在子节点,也可以直接省略
    {'id': 'J', 'left': None, 'right': None}]
# 创建根节点
btree = BinTree(root_node=root_node)
# 添加节点
btree.build_from_node_list(node_list)

2.3 二叉树遍历

# BinTree() 类 的内置函数
    # ---------------------------------------------------------
    def preorder_trav(self, subtree):  # 先(根)序遍历
        if subtree is not None:
            self.node_seq.append(subtree.id)      # 递归函数里先处理根
            self.preorder_trav(subtree.left)      # 递归处理左子树
            self.preorder_trav(subtree.right)     # 递归处理右子树
        return None
    # ---------------------------------------------------------
    def inorder_trav(self, subtree):  # 中(根)序遍历
        if subtree is not None:
            self.inorder_trav(subtree.left)        # 递归函数左子树
            self.node_seq.append(subtree.id)       # 递归处理根
            self.inorder_trav(subtree.right)       # 递归处理右子树
        return None
    # ---------------------------------------------------------
    def postorder_trav(self, subtree):  # 后(根)序遍历
        if subtree is not None:
            self.postorder_trav(subtree.left)     # 递归函数左子树
            self.postorder_trav(subtree.right)    # 递归处理右子树
            self.node_seq.append(subtree.id)      # 递归处理根
        return None
    # ---------------------------------------------------------
    def layer_trav(self, subtree):  # 层序遍历
        cur_nodes = [subtree]  # current layer nodes
        next_nodes = []
        while cur_nodes or next_nodes:
            for node in cur_nodes:
                self.node_seq.append(node.id)
                if node.left:
                    next_nodes.append(node.left)
                if node.right:
                    next_nodes.append(node.right)
            cur_nodes = next_nodes  # 继续遍历下一层
            next_nodes = []
    # ---------------------------------------------------------
    def traversal(self, method='preorder'):
      self.node_seq = []
      if method == 'preorder':
          self.preorder_trav(self.root_node)
      elif method == 'inorder':
          self.inorder_trav(self.root_node)
      elif method == 'postorder':
          self.postorder_trav(self.root_node)
      elif method == 'layer':
          self.layer_trav(self.root_node)
      return self.node_seq
    # ---------------------------------------------------------

实例

print(btree.traversal('preorder'))
print(btree.traversal('inorder'))
print(btree.traversal('postorder'))
print(btree.traversal('layer'))
# 输出
# ['A', 'B', 'D', 'E', 'H', 'C', 'F', 'G', 'I', 'J']
# ['D', 'B', 'H', 'E', 'A', 'F', 'C', 'I', 'G', 'J']
# ['D', 'H', 'E', 'B', 'F', 'I', 'J', 'G', 'C', 'A']
# ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']

2.4 可视化

    def display(self):
        from graphviz import Digraph
        
        root_node = self.root_node

        # Create Digraph object
        dot = Digraph(node_attr={'shape': 'circle', 'nodesep': '0'}, edge_attr={'arrowhead': 'vee'})

        cur_nodes = [root_node]  # current layer nodes
        next_nodes = []

        while cur_nodes or next_nodes:
            for node in cur_nodes:
                # add node
                dot.node(node.id, label=node.id)

                if (not node.left) and (not node.right):
                    pass
                else:
                    if node.left:
                        # add left edge
                        dot.edge(node.id, node.left.id)
                        next_nodes.append(node.left)
                    else: # When node.left is none
                        # add virtual left edge
                        dot.node(node.id+'l', style='invis')
                        dot.edge(node.id, node.id+'l', style='invis')

                    # add virtual middle edge
                    dot.node(node.id+'m', label="", width='0', height='0', style='invis')
                    dot.edge(node.id, node.id+'m', style='invis')

                    if node.right:
                        # add right edge
                        dot.edge(node.id, node.right.id)
                        next_nodes.append(node.right)
                    else: # When node.right is none
                        # add virtual right edge
                        dot.node(node.id+'r', label=node.id, style='invis')
                        dot.edge(node.id, node.id+'r', style='invis')

            cur_nodes = next_nodes  # 继续遍历下一层
            next_nodes = []
        return dot
    # ---------------------------------------------------------

实例

btree.display()

3 应用:二分查找树

二叉查找树(Binary Search Tree,BST)是一种特殊的二叉树,具有有以下性质:

对于任意一个节点 \(n\)

  • 其左子树(left subtree)下的每个后代节点的值都小于节点 \(n\) 的值;

  • 其右子树(right subtree)下的每个后代节点的值都大于节点 \(n\) 的值。

参考资料

[1] 14 树和二叉树, in "Python 算法与数据结构视频教程", 地址

posted @ 2022-02-16 09:31  veager  阅读(130)  评论(0编辑  收藏  举报