shan

二叉树的python实现

2020-05-28

本文为 python数据结构与算法的读书笔记,主要介绍二叉树的两种python实现方式。

定义

  1. 有一个根节点;
  2. 除了根节点外,其他每个节点都与其唯一的父节点相连;
  3. 从根节点到其他每个节点都有且仅有一条路径;
  4. 如果每个节点最多有两个子节点,就称其为二叉树。

一般要实现下面的操作:

  • 创建一个二叉树: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. 如果当前标记是),就跳到当前节点的父节点。

这个例子中,在构建解析树时,需要追踪当前节点机器父节点。我们可以通过遍历这棵树时使用栈记录父节点,每当要下沉至当前接地那的子节点时,先将当前节点压到栈中。当要返回到当前节点的父节点时,就将父节点从栈中弹出来。下面代码展示了构建的解析树:

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
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章