二叉树
最简单的树形结构,其特点是树中每个节点之多关联到两个后继节点,另一个特点是一个节点关联的后继节点明确地分左右。一颗二叉树的高度(深度)是树中节点的最大层数,树的高度是二叉树的整体性质。
如果知道了一颗二叉树的对称序列,又知道另一遍历序列,就可以唯一确定这个二叉树。
- 宽度优先遍历(按层次顺序遍历):宽度优先是按照路径长度由近及远地访问节点。对二叉树做这种遍历也就是按二叉树的层次逐层访问树中各节点。与状态空间搜索的情况一样,这种遍历不能写成一个递归过程。常见的算法是在每一层里从左到右逐个访问,实现这一算法需要用一个队列作为缓存。按照此方法遍历的节点序列称为二叉树的层次序列。
- 遍历与搜索:所有有关状态空间搜索的方法和实现技术都可以移植到二叉树遍历问题中。例如,递归的搜索方法、基于栈的非递归搜索(即深度优先遍历)。基于队列的宽度优先搜索对应于这里的层次序遍历、
二叉树的list实现
二叉树节点是一个三元组,元素是左右子树和节点数据。二叉树是递归结构,python的list也是递归结构。基于list类型很容易实现二叉树,可以用如下代码实现
def BinTree(data, left = None, right = None):
return [data, left, right]
def is_empty_BinTree(btree):
return btree is None
def root(btree):
return btree[0]
def left(btree):
return btree[1]
def right(btree):
return btree[2]
def set_root(btree, data):
btree[0] = data
def set_left(btree, left):
btree[1] = left
def set_right(btree, right):
btree[2] = right
t1 = BinTree(2, BinTree(4), BinTree(8))
优先队列
作为缓存结构,优先队列与栈和队列类似,可以将数据元素保存其中,可以访问和弹出。优先队列的特点是存入其中的每项数据都另外附有一个数值,表示这个项的优先程度,称为优先级。优先队列应该保证,在任何时候访问或弹出的,总是当时在这个结构里保存的所有元素中优先级最高的。若该元素不弹出,再次访问还将得到它。一种简单的实现方法是基于建续表技术实现优先队列。
class ProiQue:
def __init__(self, elist= []):
self.__elems = list(elist)
self.__elems.sort(reverse= True)
def enqueue(self, e):
i = len(self.__elems) - 1
#保证优先度相同元素的正确排列顺序,使同优先级的元素先进先出
while i >= 0:
if self.__elems[i] <= e:
i -= 1
else:
break
self.__elems.insert(i+1, e)
def is_empty(self):
return not self.__elems
def peek(self):
return self.__elems[-1]
def dequeue(self):
return self.__elems.pop()
考虑各操作的效率:插入元素是O(n), 其他都是O(1)操作。注意,即使插入时表的存储区满,操作效率仍为O(n)。实际上,我们也可以用链接技术事项优先队列,几个主要操作的复杂度情况与连续表类似。总之,无论采用怎样的具体实现技术,在插入元素与取出元素的操作中总有一种是具有线性复杂度的操作。下面考虑改善优先队列的操作性能的可能性。
树形结构和堆
采用树形结构实现优先队列的一种有效技术称为堆。从结构上看,堆就是节点里存储数据的完全二叉树,但同时也满足堆序:任何一个节点里所村的数据先于或等于其子节点里的数据,即
- 在一个堆中从树根到任何一个叶节点的路径上,各节点里所寸的数据按规定的优先关系递减。
- 堆中最优先的元素必定位于二叉树的根节点里,O(1)时间就能得到
- 位于树中不同路径上的元素,不关心其顺序关系。
在下面的讨论中提到堆时,经常是指存储在连续表中的一颗完全二叉树,其元素的存储情况形成了一个堆。由于这种结构与堆一一对应,可以不区分这两个概念。
堆排序
def heap_sort(elems):
def siftdown(elems, e, begin, end):
i = begin
j = begin * 2 + 1
while j < end:
if j+1 < end and elems[j+1] < elems[j]:
j += 1
if e < elems[j]: #e在三者中最小
break
elems[i] = elems[j] #elems[j]最小,上移
i, j = j, 2*j+1
elems[i] = e
end = len(elems)
for i in range(end//2, -1, -1):
siftdown(elems, elems[i], i, end)
for i in range((end-1),0,-1):
e = elems[i]
elems[i] = elems[0]
siftdown(elems, e, 0, i)
向下筛选siftdownh的实现里是拿着新元素找位置,没有采用先存入元素再逐步交换的方法,可以稍微节省一点开销。主函数体主要是两个循环:第一个循环建堆,从位置i开始,以end为建堆范围的边界;第二个循环逐个取出最小元素,将其累计在表的最后,放一个退一步。
非递归方法可用栈实现:
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()
用python写程序,在考虑遍历数据汇集结构的时候,可以用迭代器。在讨论链表时,提到过elements和filter。简单修改上述非递归先序遍历函数,我们可以得到一个二叉树迭代器。非递归遍历算法另一重要用途就是作为实现迭代器的基础。
def preorder_elements(t):
s = 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()