二叉树的概念继承自树。树是一种重要的非线性数据结构,在文件系统、图形用户界面、数据库、网站等系统中有着极其广泛的应用。一棵树的定义如下:
通常我们将树 T T T 定义为存储一系列元素的有限节点集合,这些节点具有父亲-儿子关系并且满足如下属性:
- 如果树 T T T 不为空,则它一定具有一个称为根节点的特殊节点,并且该节点没有父节点。
- 每个非根节点 v v v 都具有唯一的父亲节点 w w w ,每个具有父节点 w w w 的节点都是节点 w w w 的一个儿子。
此外,定义同一个父节点的儿子节点之间是兄弟关系。一个没有儿子的节点称为外部节点或叶子节点。一个有一个或多个儿子的节点称为内部节点。节点之间还可以同理定义祖先和子孙关系等。
对于树 T T T 的一个节点 p p p ,其儿子节点的数量称为 p p p 节点的度。显然,叶子节点的度都为零。一棵树的度定义为其所有节点度的最大值。树还具有深度和高度。假定 p p p 是树 T T T 中的一个节点,那么 p p p 的深度就是节点 p p p 的祖先个数,不包括 p p p 本身。一个节点的高度定义为其子节点中的最大高度加一,且叶子节点的高度为零。一棵非空树的高度等于其所有叶子节点深度的最大值。具有相同深度的节点称为在同一层。定义一棵树的宽度为每一层节点数量的最大值。
而二叉树作为一种特殊的树,除了具有树的属性之外,还有以下规则:
如果除了最后一个叶子节点的父节点以外,每个节点都有零个或两个子节点,则称这样的二叉树为完全二叉树。如果除了叶子节点之外的每一个节点都有两个儿子,且每一层都被填满,则称这样的二叉树为完美二叉树。如果除了叶子节点之外的每一个节点都有两个儿子,则称这样的二叉树为满二叉树。
若树的儿子之间有顺序,则称这种树为有序树。有序树支持前序遍历、后序遍历和层次遍历三种遍历方式。二叉树还多支持一个中序遍历。其中,前中后表示根节点的遍历位置,前序遍历就是先遍历根节点然后遍历左子树和右子树,中序遍历就是先遍历左子树再遍历根节点然后遍历右子树,后序遍历同理。层次遍历就是一层一层地从左到右遍历。
下面将尝试用Python来实现二叉树的数据结构。
二叉树有链式存储法和顺序存储法。本文将采用链式存储。
首先是节点类:
class Node:
__slots__ = '_value', '_father', '_lson', '_rson'
def __init__(self, value=None, father=None, lson=None, rson=None):
self._value = value
self._father = father
self._lson = lson
self._rson = rson
def father(self):
return self._father
def lson(self):
return self._lson
def rson(self):
return self._rson
然后是二叉树主类。由于我们只要抓住了二叉树的根节点,就可以根据节点间的父亲-儿子关系进行遍历。所以二叉树类只要存储根节点即可。
class BinaryTree:
def __init__(self, root=None):
self._root = Node(root)
def root(self):
return self._root
至此,在逻辑上,我们已经建立了一棵二叉树。不过现在它还很瘦弱,我们需要向其中添加一些方法来使它健壮起来。
由高度、深度、宽度的定义,可写出以下方法来分别求出:
## Node
def degree(self):
return sum(1 for son in self.sons())
def height(self):
return 1 + max(son.height() for son in self.sons()) if self.degree() else 0
def depth(self):
return 1 + self._father.depth() if self._father else 0
## BinaryTree
def height(self):
return self.root().height()
def degree(self):
return max(node.degree() for node in self._nodes())
其中self.sons()
和self._nodes()
方法是生成器,实现方式如下:
## Node
def sons(self):
for son in (self._lson, self._rson):
if son:
yield son
## BinaryTree
def _nodes(self):
return self.preorder()
这里_nodes()
直接调用self.preorder()
方法,即按前序遍历顺序给出结点,实际操作中可以根据需要自行调整。以下是前、中、后、层,四种遍历的实现方式:
## BinaryTree
def preorder(self):
for node in self._preorder_aux(self._root):
yield node
def _preorder_aux(self, root):
yield root
for son in root.sons():
for other in self._preorder_aux(son):
yield other
def postorder(self):
for node in self._postorder_aux(self._root):
yield node
def _postorder_aux(self, root):
for son in root.sons():
for other in self._postorder_aux(son):
yield other
yield root
def inorder(self):
for node in self._inorder_aux(self._root):
yield node
def _inorder_aux(self, root):
if root.lson():
for other in self._inorder_aux(root.lson()):
yield other
yield root
if root.rson():
for other in self._inorder_aux(root.rson()):
yield other
def levelorder(self):
queue = [self.root()]
while len(queue):
node = queue.pop(0)
yield node
for son in node.sons():
queue.append(son)
此外,不能打印的二叉树是不够形象的,因此我们还要重载一下__repr__()
方法和__str__()
方法:
## Node
def __repr__(self):
return str(self._value)
def __str__(self):
lines = _tree_print_aux(self)[0]
return '\n'.join(line.rstrip() for line in lines)
## BinaryTree
def __repr__(self):
return self._root.__repr__()
def __str__(self):
return self._root.__str__()
其中的_tree_print_aux()
方法是用来辅助输出的,该方法借鉴自官方模块binarytree
的_build_tree_string()
函数。实现如下:
def _tree_print_aux(root, curr=0):
if root is None:
return [], 0, 0, 0
line_l = []
line_r = []
node_repr = str(root._value)
new_root_width = gap_size = len(node_repr)
l_box, l_box_width, l_root_start, l_root_end = \
_tree_print_aux(root._lson, curr<<1|1)
r_box, r_box_width, r_root_start, r_root_end = \
_tree_print_aux(root._rson, (curr<<1) + 2)
if l_box_width > 0:
l_root = ((l_root_start + l_root_end)>>1) + 1
line_l.append(' ' * (l_root + 1))
line_l.append('_' * (l_box_width - l_root))
line_r.append(' ' * l_root + '/')
line_r.append(' ' * (l_box_width - l_root))
new_root_start = l_box_width + 1
gap_size += 1
else:
new_root_start = 0
line_l.append(node_repr)
line_r.append(' ' * new_root_width)
if r_box_width > 0:
r_root = (r_root_start + r_root_end)>>1
line_l.append('_' * r_root)
line_l.append(' ' * (r_box_width - r_root + 1))
line_r.append(' ' * r_root + '\\')
line_r.append(' ' * (r_box_width - r_root))
gap_size += 1
new_root_end = new_root_start + new_root_width - 1
gap = ' ' * gap_size
new_box = [''.join(line_l), ''.join(line_r)]
for i in range(max(len(l_box), len(r_box))):
l_line = l_box[i] if i < len(l_box) else ' ' * l_box_width
r_line = r_box[i] if i < len(r_box) else ' ' * r_box_width
new_box.append(l_line + gap + r_line)
return new_box, len(new_box[0]), new_root_start, new_root_end
刚才所写的方法都是在一棵二叉树已经建立好的情况下查看它的相关性质,下面我们来着手写一下二叉树的建立和更新相关的方法:
## BinaryTree
def __setitem__(self, index, node):
parent_index = index >> 1
parent = self[parent_index]
if index & 1:
parent._rson = node
else:
parent._lson = node
def build(self, lst):
self._root = Node(lst[0])
for data in lst[1::]:
self.insert(data)
def insert(self, value):
if self.is_empty():
self._root = Node(value)
else:
index = len(self)
self[index+1] = Node(value)
这三个方法都默认树上的结点按照从上到下、从左到右的顺序,且根节点的序号为1。
上述方法还需要重载__getitem__()
和__len__()
方法以及is_empty()
方法,实现如下:
## BinaryTree
def __getitem__(self, index):
queue = [self.root()]
curr = 1
flag = True
while flag:
flag = False
new_queue = []
for node in queue:
if curr == index:
return node
curr += 1
if node is None:
new_queue.extend((None, None))
continue
new_queue.extend(node.sons())
flag = bool(node.degree())
queue = new_queue
def __len__(self):
return sum(1 for node in self._nodes())
def is_empty(self):
return self.root()._value is None
将上述代码汇总,并编写测试程序如下:
class Node:
__slots__ = '_value', '_father', '_lson', '_rson'
def __init__(self, value=None, father=None, lson=None, rson=None):
self._value = value
self._father = father
self._lson = lson
self._rson = rson
def father(self):
return self._father
def lson(self):
return self._lson
def rson(self):
return self._rson
def degree(self):
return sum(1 for son in self.sons())
def height(self):
return 1 + max(son.height() for son in self.sons()) if self.degree() else 0
def depth(self):
return 1 + self._father.depth() if self._father else 0
def sons(self):
for son in (self._lson, self._rson):
if son:
yield son
def __repr__(self):
return str(self._value)
def __str__(self):
lines = _tree_print_aux(self)[0]
return '\n'.join(line.rstrip() for line in lines)
class BinaryTree:
def __init__(self, root=None):
self._root = Node(root)
def root(self):
return self._root
def height(self):
return self.root().height()
def degree(self):
return max(node.degree() for node in self._nodes())
def _nodes(self):
return self.preorder()
def preorder(self):
for node in self._preorder_aux(self._root):
yield node
def _preorder_aux(self, root):
yield root
for son in root.sons():
for other in self._preorder_aux(son):
yield other
def postorder(self):
for node in self._postorder_aux(self._root):
yield node
def _postorder_aux(self, root):
for son in root.sons():
for other in self._postorder_aux(son):
yield other
yield root
def inorder(self):
for node in self._inorder_aux(self._root):
yield node
def _inorder_aux(self, root):
if root.lson():
for other in self._inorder_aux(root.lson()):
yield other
yield root
if root.rson():
for other in self._inorder_aux(root.rson()):
yield other
def levelorder(self):
queue = [self.root()]
while len(queue):
node = queue.pop(0)
yield node
for son in node.sons():
queue.append(son)
def __repr__(self):
return self._root.__repr__()
def __str__(self):
return self._root.__str__()
def __setitem__(self, index, node):
parent_index = index >> 1
parent = self[parent_index]
if index & 1:
parent._rson = node
else:
parent._lson = node
def build(self, lst):
self._root = Node(lst[0])
for data in lst[1::]:
self.insert(data)
def insert(self, value):
if self.is_empty():
self._root = Node(value)
else:
index = len(self)
self[index+1] = Node(value)
def __getitem__(self, index):
queue = [self.root()]
curr = 1
flag = True
while flag:
flag = False
new_queue = []
for node in queue:
if curr == index:
return node
curr += 1
if node is None:
new_queue.extend((None, None))
continue
new_queue.extend(node.sons())
flag = bool(node.degree())
queue = new_queue
def __len__(self):
return sum(1 for node in self._nodes())
def is_empty(self):
return self.root()._value is None
def _tree_print_aux(root, curr=0):
if root is None:
return [], 0, 0, 0
line_l = []
line_r = []
node_repr = str(root._value)
new_root_width = gap_size = len(node_repr)
l_box, l_box_width, l_root_start, l_root_end = \
_tree_print_aux(root._lson, curr<<1|1)
r_box, r_box_width, r_root_start, r_root_end = \
_tree_print_aux(root._rson, (curr<<1) + 2)
if l_box_width > 0:
l_root = ((l_root_start + l_root_end)>>1) + 1
line_l.append(' ' * (l_root + 1))
line_l.append('_' * (l_box_width - l_root))
line_r.append(' ' * l_root + '/')
line_r.append(' ' * (l_box_width - l_root))
new_root_start = l_box_width + 1
gap_size += 1
else:
new_root_start = 0
line_l.append(node_repr)
line_r.append(' ' * new_root_width)
if r_box_width > 0:
r_root = (r_root_start + r_root_end)>>1
line_l.append('_' * r_root)
line_l.append(' ' * (r_box_width - r_root + 1))
line_r.append(' ' * r_root + '\\')
line_r.append(' ' * (r_box_width - r_root))
gap_size += 1
new_root_end = new_root_start + new_root_width - 1
gap = ' ' * gap_size
new_box = [''.join(line_l), ''.join(line_r)]
for i in range(max(len(l_box), len(r_box))):
l_line = l_box[i] if i < len(l_box) else ' ' * l_box_width
r_line = r_box[i] if i < len(r_box) else ' ' * r_box_width
new_box.append(l_line + gap + r_line)
return new_box, len(new_box[0]), new_root_start, new_root_end
if __name__ == '__main__':
bt = BinaryTree()
bt.build(list(range(10)))
print(bt)
print(bt.height())
print(bt.degree())
print(list(bt.preorder()))
print(list(bt.postorder()))
print(list(bt.inorder()))
print(list(bt.levelorder()))
输出如下:
____0__
/ \
__1__ 2
/ \ / \
3 4 5 6
/ \ /
7 8 9
3
2
[0, 1, 3, 7, 8, 4, 9, 2, 5, 6]
[7, 8, 3, 9, 4, 1, 5, 6, 2, 0]
[7, 3, 8, 1, 9, 4, 0, 5, 2, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]