二叉树的结点类
二叉树由一组结点组成,结点通过链接引用其子结点,没有子结点时用 None N o n e 值,空二叉树用 None N o n e 表示。
class BinTNode:
def __init__(self, dat, left=None, right=None):
self.data = dat
self.left = left
self.right = right
这个结点类接受三个参数,分别为其结点数据和左右子结点,两个参数都用默认值时构造出的为叶结点。
下面语句构造了一个包含三个结点的二叉树:
t1 = BinTNode(1, BinTNode(2), BinTNode(3))
基于BinTNode类构造的二叉树具有递归的结构,所以很容易采用递归的方式处理,下面两个函数定义展示了处理这种二叉树的典型技术:
#统计树中结点个数
def count_BinTNodes(t):
if t is None:
return 0
else:
return 1 + count_BinTNodes(t.left) + count_BinTNodes(t.right)
#假设结点中保存数值,求二叉树中所有数值的和
def sum_BinTNodes(t):
if t is None:
return 0
else:
return t.data + sum_BinTNodes(t.left) + sum_BinTNodes(t.right)
可以看出,递归定义的二叉树操作具有统一的模式:
- 描述对空树的处理,应直接给出结果。
- 描述非空树情况的处理:
1.如何处理根节点。
2.通过递归调用分别处理左,右子树。
3.基于上述三个部分处理的结果,得到对整个树的处理结果。
遍历算法
递归定义的遍历函数
采用递归方式实现的是按深度优先方式遍历二叉树。
def preorder(t, proc):
if t is None:
return
proc(t.data)
preorder(t.left)
preorder(t.right)
为了能看到二叉树的具体情况,现在定义一个以易读形式输出二叉树的函数,这里采用带括号的前缀形式输出,为显示空子树输出一个符号“^”。
from __future__ import print_function #python2的print不支持end=,所以import print_function
def print_BinTNodes(t):
if t is None:
print ('^', end='')
return
print ('(' + str(t.data), end='')
print_BinTNodes(t.left)
print_BinTNodes(t.right)
print (')', end='')
t = BinTNode(1, BinTNode(2, BinTNode(5)), BinTNode(3))
print_BinTNodes(t)
#输出:
(1(2(5^^)^)(3^^))
宽度优先遍历
要实现宽度优先遍历二叉树,需要一个队列,下面的定义使用了之前的SQueue类:
from SQueue import *
def levelorder(t, proc):
qu = SQueue()
qu.enqueue(t)
while not qu.is_empty():
n = qu.dequeue() #从队列中取出一个处理
if n is None: #如果取出的结点为空,不做任何处理,返回循环
continue
qu.enqueue(n.left) #把取出结点的左/右结点入队
qu.enqueue(n.right)
proc(n.data) #处理当前节点,完成后循环队列的下一个目标
非递归的先根序遍历函数
这里需要一个栈来保存未处理的子树,基本想法为:
- 由于采用先根序,遇到结点就可以访问,下一步应该沿着树的左枝下行。
- 但结点的右分支还没有访问,因此需要记录,将右子结点入栈。
- 遇到空树时回溯,取出栈里保存的一个右分支,再重复上述步骤
上述三个条件包含两个循环:1.访问该结点,把右结点入栈,再访问左结点,直到左结点为空,停止循环,转入第二步;2.从栈中取出一个,继续第一步,每次访问一个左结点都说明它的兄弟结点和父节点已经访问过,如果不能顺着该结点继续访问,就应该再从栈中取出一个,知道最后取来的结点为空或者栈中没有结点。
def preorder_nonrec(t, proc):
s = SStack()
while t is not None or not s.is_empty():
while t is not None:
proc(t.data)
s.push(t.right)
t = t.left
t = s.pop()
通过生成器函数遍历
def preorder_elements(t):
s = LStack()
while t is not None or not s.is_empty():
while t is not None:
yield t.data
s.push(t.right)
t = t.left
t = s.pop()
print('**************************')
for i in preorder_elements(t):
print(i)
二叉树类
基于结点构造的二叉树具有递归结构,可以很方便处理,但也有不足:用None表示空树,但None的类型并不是BinTNode,而且不是一种很好的数据封装类型。下面定义一个二叉树类,它接受前面的BinTNode作为结点链接成树形结构:
class BinTree:
def __init__(self):
self._root = None
def is_empty(self):
return self._root is None
def root(self):
return self._root
def leftchild(self):
if self._root is None:
raise ValueError("The Tree is empty!")
return self._root.left
def rightchild(self):
if self._root is None:
raise ValueError("The Tree is empty!")
return self._root.right
def set_root(self, rootnode):
self._root = rootnode
def set_left(self, leftchild):
self._root.left = leftchild
def set_right(self, rightchild):
self._root.right = rightchild
#定义一个元素迭代器
def preorder_elements(self):
t, s = self._root, SStack()
while t is not None or not s.is_empty():
while t is not None:
s.push(t.right)
yield t.data
t = t.left
t = s.pop()
和链接表一样,无法通过一个二叉树结点找到它的父结点,可以为结点类添加一个链接指向它的父结点。
在old-style class中,任意instance的type都是’instance’。所以绝对不能用type来判断其类型。
另外这个问题又与Python的思想有关,正常情况下不应该编写代码检查类型的,而应该直接假设被操作的instance具有你希望的属性,否则抛出异常。即使需要检查类型,也应该用isinstance来判断,这样你期望类型的subclass也能正常被处理(比如,一个函数需要处理Message类型,那么它应该也能处理Message的子类型MyMessage,所以应该使用isinstance(arg,Message)这样来判断而不是type(arg) == Message来判断)。
>>> from collections import Iterator
>>> class A(object):
... def __iter__(self):
... pass
... def next(self):
... pass
...
>>> isinstance(A(), Iterator)
True