本文为 python数据结构与算法的读书笔记,主要介绍二叉树的两种python实现方式。
定义
- 有一个根节点;
 
- 除了根节点外,其他每个节点都与其唯一的父节点相连;
 
- 从根节点到其他每个节点都有且仅有一条路径;
 
- 如果每个节点最多有两个子节点,就称其为二叉树。
 
一般要实现下面的操作:
- 创建一个二叉树:
BinaryTree(); 
- 返回当前节点的左子节点所对应的二叉树:
get_left_child(); 
- 返回当前节点的右子节点所对应的二叉树:
get_right_child(); 
- 在当前节点存储参数val中的对象:
set_root_val(); 
- 返回当前节点存储的对象:
get_root_val(); 
- 新建一个二叉树,并将其作为当前节点的左子节点:
insert_left(val); 
- 新建一颗二叉树,并将其作为当前节点的右子节点:
insert_right(val); 
实现
实现树的关键在于选择内部存储技巧,在Python中,我们选用了两种实现方式,分别为:列表之列表,节点与引用。
列表之列表
这种方式我们主要使用列表作为树的存储方法,将根节点作为列表中的第一个元素的值,第二个元素作为左子树的列表,第三个元素作为右子树的列表。如下图:
上图用python列表表示为:
1 2 3 4 5 6 7 8 9
   | my_tree = [a,             [b,              [d,               [h, [], []]],              [e, [], []]],             [c,              [f, [], []],              [g, [], []]            ]]
   | 
 
可以通过标准的列表访问方式访问子树,树的根节点是my_tree[0],左子树是my_tree[1],右子树是my_tree[2]。上面关于列表的结果和树的结构很类似,并且是递归的。由一个根节点和两个空列表表示的是一个叶子节点。这种表示方法不仅可以用在表示二叉树上,还可以推广到多子树的情况。
下面提供一些方法,可以便利的将上面这种列表作为树来使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
   | def BinaryTree(r):     """     创建二叉树     """     return [r, [], []]
 
  def insert_left(root, new_branch):     """     插入左子树     """     t = root.pop(1)     if len(t) > 1:         root.insert(1, [new_branch, t, []])     else:         root.insert(1, [new_brach, [], []])              return root
 
  def insert_right(root, new_branch):     """     插入右子树     """     t = root.pop(2)     if len(t) > 1:         root.insert(2, [new_branch, [], t])     else:         root.insert(2, [new_branch, [], []])     return root
 
 
  def get_root_val(root):     return root[0]
 
  def set_root_val(root, new_val):     root[0] = new_val           def get_left_child(root):     return root[1]
 
  def get_right_child(root):     return root[2]
   | 
 
节点与引用
树在python中第二种实现方式是利用节点与引用进行创建。我们将定义一个类,其中有根节点和左右子树的属性。采用节点与引用表示法,可以将树想象成如下图所示的结构:
首先得定义一个简单的类,节点与引用的要点是,属性left和right指向BinaryTree类的其他实例。具体来说,就是在向树中插入新的左子树时,我们会创建另一个BinaryTree实例,并将根节点的self.left_child改为指向新树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   | class BinaryTree:     def __init__(self, root_obj):         self.key = root_obj         self.left_child = None         self.right_child = None              def insert_left(self, new_node):         """         插入左子树         """         if self.left_child is None:             self.left_child = BinaryTree(new_node)         else:             t = BinaryTree(new_node)             t.left_child = self.left_child             self.left_child = t                  def insert_right(self, new_node):         """         插入右子树         """         if self.right_child is None:             self.right_child = BinaryTree(new_node)         else:             t = BinaryTree(new_node)             t.right_child = self.right_child             self.right_child = t               def get_right_child(self):         return self.right_child          def get_left_child(self):         return self.left_child          def set_root_val(self, obj):         self.key = obj              def get_root_val(self):         return self.key
   | 
 
应用
解析树
我们用一个数学表达式来对解析树的构建进行说明。数学表达式:$ ((7 + 3) * (5 - 2))$ 。这是完全括号表达式。利用树的层次结构,再计算完子树的表达式后,只需要一个节点代替整颗子树即可。整个数学表达式用解析树表示为下图:
构建解析树的第一步是将表达式拆分为标记列表。需要考虑4种标记:左括号、右括号、运算符合操作数。左括号代表起点,所以应该创建一棵对应表达式的新树。反之遇到右括号意味着到达该表达式的终点。操作数即是叶子节点,也是其运算符的子节点。每个运算符都有左右节点。
我们可以定以下四条规则:
- 如果当前标记是(,就为当前节点添加一个左子节点,并下沉至该子节点;
 
- 如果当前标记在 +,-,/,*中,就将当前节点的值设置为当前标记对应的运算符;为当前节点添加一个右子节点,并下沉至该子节点;
 
- 如果当前标记是数字,就将当前节点的值设置为这个数并返回至父节点;
 
- 如果当前标记是),就跳到当前节点的父节点。
 
这个例子中,在构建解析树时,需要追踪当前节点机器父节点。我们可以通过遍历这棵树时使用栈记录父节点,每当要下沉至当前接地那的子节点时,先将当前节点压到栈中。当要返回到当前节点的父节点时,就将父节点从栈中弹出来。下面代码展示了构建的解析树:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | from pythonds.basic import Stack from pythonds.trees import BinaryTree
 
  def build_parse_tree(fpexp):     fplist = fpexp.split(' ')     pstack = Stack()     etree = BinaryTree(' ')     pstack.push(etree)     current_tree = etree     for i in fplist:         if i == "(":             current_tree.insertLeft(' ')             pstack.push(current_tree)             current_tree = current_tree.getLeftChild()         elif i not in '+-*/)':             current_tree.setRootVal(eval(i))             parent = pstack.pop()             current_tree = parent
          elif i in '+-*/':             current_tree.setRootVal(i)             current_tree.insertRight(' ')             pstack.push(current_tree)
          elif i == ')':             current_tree = pstack.pop()
          else:             raise ValueError("Unknown Operator: " + i)
      return etree
   |