什么是二叉搜索树
一颗二叉搜索树是以一颗二叉树来组织的,如下图所示,这样一颗树可以使用一个链表数据结构来表示,其中每个节点就是一个对象,每个节点有自己的key,还包含属性
l
e
f
t
、
r
i
g
h
t
和
p
left{、} right{和} p
left、right和p,分别指向节点的左孩子、右孩子和双亲,如果某个孩子节点和父节点不存在,则相应属性值为
N
I
L
NIL
NIL,根节点是树中唯一父指针为
N
I
L
NIL
NIL的节点。
假设节点
x
x
x的其中
l
e
f
t
和
r
i
g
h
t
left{和}right
left和right分别是节点
x
x
x的左子树和右子树的一个节点,则二叉搜索树中的关键字存储性质:
l
e
f
t
.
k
e
y
≤
x
.
k
e
y
≤
r
i
g
h
t
.
k
e
y
left.key \le x.key \le right.key
left.key≤x.key≤right.key
如上图左边,树根的关键字6,在其左子树中2、5和5,它们均不大于6,而在其右子树中有关键字7和8,它们均不小于6。这个性质对树中的每个节点都成立。
二叉搜索树构建python实现:
#构造节点
class Node(object):
def __init__(self, key, parent=None,lchild=None, rchild=None):
self.key = key
self.parent = parent
self.lchild = lchild
self.rchild = rchild
#二叉搜索树
class BinaryTree(object):
def __init__(self,root=None):
self.root = root
#构建二叉搜索树,插入节点
def insert(self, cur):
y = None
x = self.root
while x!=None:
#y记录插入的节点cur对应的parent节点
y = x
#对比关键字值,判断cur是插入左边还是右边
if cur.key < x.key:
x = x.lchild
else:
x = x.rchild
cur.parent = y
if y==None:
#第一次插入节点,树为空
self.root = cur
#判断插入左子树还是右子树
elif cur.key < y.key:
y.lchild = cur
else:
y.rchild = cur
二叉树的遍历
二叉搜索树性质允许我们通过一个简单的递归算法来按序输出二叉树中的所有关键字,这种算法称为中序遍历算法,根据输出子树根的关键字与左子树和右子树的关键字顺序,有如下三种数的遍历算法:
- 中序遍历(inorder tree walk):输出子树根的关键字位于其左子树的关键字和右子树关键字值之间,如上图中,中序遍历为:2,5,5,6,7,8
- 先序遍历(preorder tree walk):输出子树根的关键字位于其左子树的关键字和右子树关键字值之前,如上图中,先序遍历为:6,5,2,5,7,8
- 后序遍历(postorder tree walk):输出子树根的关键字位于其左子树的关键字和右子树关键字值之后,如上图中,先序遍历为:2,5,5,8,7,6
二叉树遍历,python实现:
#先序遍历
def preorder_tree_walk(self, tree):
if tree is not None:
print(tree.key, end=" ")
self.preorder_tree_walk(tree.lchild)
self.preorder_tree_walk(tree.rchild)
#中序遍历
def inorder_tree_walk(self, tree):
if tree is not None:
self.inorder_tree_walk(tree.lchild)
print(tree.key, end=" ")
self.inorder_tree_walk(tree.rchild)
#后序遍历
def postorder_tree_walk(self, tree):
if tree is not None:
self.preorder_tree_walk(tree.lchild)
self.preorder_tree_walk(tree.rchild)
print(tree.key, end=" ")
查询二叉搜索树
查找
从树根开始查找,遇到每个节点
x
x
x,比较关键字
k
k
k与
x
.
k
e
y
x.key
x.key,如果两个关键字相等,查找终止,如果
k
k
k小于
x
.
k
e
y
x.key
x.key,查找在
x
x
x的左子树中继续,因为二叉搜索性质蕴涵了
k
k
k不可能存储在右子树中,相反,如果
k
k
k大于
x
.
k
e
y
x.key
x.key,查找在右子树中继续。如下图所示二叉搜索树,为了查找这棵树的关键字为13的节点,从树根开始沿着 15—>6—>7—>13路径进行查找。
采用while循环来展开递归,对于大多数计算机,迭代版本的效率要高的多。
python实现关键字
k
k
k的查找:
#查找关键字为key的节点
def search(self, cur, key):
while cur !=None and key!=cur.key:
if key < cur.key:
cur = cur.lchild
else:
cur = cur.rchild
return cur
最大和最小关键字元素
如上图所示,这颗树种最小的关键字为2,它是从树根开始一直沿着lchild指针被找到的;最大关键字20是从树根开始一直沿着rchild指针被找到的。
python实现最大,最小关键字元素查找如下:
#查找最小关键字元素
def minimum(self, cur):
while cur.lchild!=None:
cur = cur.lchild
return cur
#查找最大关键字元素
def maximum(self, cur):
while cur.rchild!=None:
cur = cur.rchild
return cur
后继和前驱
给定一颗二叉搜索树中的一个节点,有时候需要按中序遍历的次序查找它的后继。如果所有的关键字互不相同,则一个节点
x
x
x的后继是大于
x
.
k
e
y
x.key
x.key的最小关键字的节点,一颗二叉搜索树的结构允许我们通过没有任何关键字的比较来确定一个节点的后继,如上图二叉树所示,关键字为15的节点的后继是关键字为17的节点,因为它是15的右子树中的最小关键字,关键字为13的节点没有右子树,因此它的后继是最低的祖先并且其左孩子也是一个祖先,这种情况下,关键字为15的节点就是它的后继。
python实现查找节点的后继节点如下:
#查找节点cur的后继
def successor(self, cur):
#如果节点的右孩子不为空,那么x的后继恰是x右子树的最左节点,调用minimum(cur.rchild)可以找到
if cur.rchild!=None:
return minimum(cur.rchild)
y = cur.paren
#后继节点为最低的祖先,并且该节点的后继节点的左孩子也是该节点的祖先
while y!=None and cur == y.rchild:
cur = y
y = y.parent
return y
节点的删除
从一颗二叉树T中删除一个节点z的整个策略分如下几种情况:
- 如果z没有左孩子,如下图(a),用其右孩子替换z
- 如果z仅有一个孩子且为其左孩子,如下图(b),那么用其左孩子来替换z
- 否则,z既有一个左孩子又有一个右孩子,我们要查找z的后继y,这个后继位于z的右子树中并且没有左孩子,需要将移出原来的位置进行拼接,并替换树中z
- 如果y是z的右孩子,那么用y替换z,如下图(c)并仅留下y的右孩子
- 否则,y位于z的右子树中,但并不是z的右孩子,如下图d所示,先用y的右孩子替换y,然后再用y替换z
为了在二叉搜索树内移动子树,定义一个子过程transplant,它是用另一颗树替换一颗子树并成为其双亲的孩子节点,用一颗以 v v v为根的子树替换一颗以 u u u为根的子树时,节点 u u u的双亲就变为节点 v v v的双亲,并且最后 v v v成为 u u u的双亲的相应孩子
替换操作,python实现如下:
def transplant(self,u,v):
#当u为树根的情况
if u.parent == None:
self.root = v
#更新u.parent.lchid
elif u == u.parent.lchild:
u.parent.lchild = v
#更新u.parent.rchild
else:
u.parent.rchild = v
#允许v为None,若不为None,更新v.parent
if v!=None:
v.parent = u.parent
利用现成的transplant过程,下面是从二叉搜索树T中删除节点cur的删除过程
def delete(self, cur):
#cur 节点没有左孩子
if cur.lchild == None:
self.transplant(cur, cur.rchild)
# cur 有一个左孩子但没有右孩子
elif cur.rchild == None:
self.transplant(cur, cur.lchild)
#cur有两个孩子
else:
#获取cur的后继节点
y = self.minimum(cur.rchild)
#如果y不是cur的左孩子,用y的右孩子替换y并成为y的双亲的一个孩子,
#然后将z的右孩子转变为y的右孩子
if y.parent != cur:
self.transplant(y,y.rchild)
y.rchild = cur.rchild
y.rchild.parent = y
#用y替换z,并成为z的双亲的一个孩子,再用z的左孩子替换y的左孩子
self.transplant(cur,y)
y.lchild = cur.lchild
y.lchild.parent = y
在一颗高度为
h
h
h的二叉搜索树上,实现动态集合操作insert和delete的运行时间均为
O
(
h
)
O(h)
O(h)
合并上述对二叉搜索树的操作流程,整个代码如下:
# -*- coding:utf8 -*-
import sys
#构造节点
class Node(object):
def __init__(self, key, parent=None,lchild=None, rchild=None):
self.key = key
self.parent = parent
self.lchild = lchild
self.rchild = rchild
#二叉搜索树
class BinaryTree(object):
def __init__(self,root=None):
self.root = root
#构建二叉搜索树,插入节点
def insert(self, cur):
y = None
x = self.root
while x!=None:
#y记录插入的节点cur对应的parent节点
y = x
#对比关键字值,判断cur是插入左边还是右边
if cur.key < x.key:
x = x.lchild
else:
x = x.rchild
cur.parent = y
if y==None:
#第一次插入节点,树为空
self.root = cur
#判断插入左子树还是右子树
elif cur.key < y.key:
y.lchild = cur
else:
y.rchild = cur
#先序遍历
def preorder_tree_walk(self, tree):
if tree is not None:
print(tree.key, end=" ")
self.preorder_tree_walk(tree.lchild)
self.preorder_tree_walk(tree.rchild)
#中序遍历
def inorder_tree_walk(self, tree):
if tree is not None:
self.inorder_tree_walk(tree.lchild)
print(tree.key, end=" ")
self.inorder_tree_walk(tree.rchild)
#后序遍历
def postorder_tree_walk(self, tree):
if tree is not None:
self.postorder_tree_walk(tree.lchild)
self.postorder_tree_walk(tree.rchild)
print(tree.key, end=" ")
#查找关键字为key的节点
def search(self, cur, key):
while cur !=None and key!=cur.key:
if key < cur.key:
cur = cur.lchild
else:
cur = cur.rchild
return cur
#查找最小关键字元素
def minimum(self, cur):
while cur.lchild!=None:
cur = cur.lchild
return cur
#查找最大关键字元素
def maximum(self, cur):
while cur.rchild!=None:
cur = cur.rchild
return cur
#查找节点cur的后继
def successor(self, cur):
#如果节点的右孩子不为空,那么x的后继恰是x右子树的最左节点,调用minimum(cur.rchild)可以找到
if cur.rchild!=None:
return self.minimum(cur.rchild)
y = cur.parent
#后继节点为最低的祖先,并且该节点的后继节点的左孩子也是该节点的祖先
while y!=None and cur == y.rchild:
cur = y
y = y.parent
return y
#移动子树过程
def transplant(self,u,v):
#当u为树根的情况
if u.parent == None:
self.root = v
#更新u.parent.lchid
elif u == u.parent.lchild:
u.parent.lchild = v
#更新u.parent.rchild
else:
u.parent.rchild = v
#允许v为None,若不为None,更新v.parent
if v!=None:
v.parent = u.parent
#delete节点
def delete(self, cur):
#cur 节点没有左孩子
if cur.lchild == None:
self.transplant(cur, cur.rchild)
# cur 有一个左孩子但没有右孩子
elif cur.rchild == None:
self.transplant(cur, cur.lchild)
#cur有两个孩子
else:
#获取cur的后继节点
y = self.minimum(cur.rchild)
#如果y不是cur的左孩子,用y的右孩子替换y并成为y的双亲的一个孩子,
#然后将z的右孩子转变为y的右孩子
if y.parent != cur:
self.transplant(y,y.rchild)
y.rchild = cur.rchild
y.rchild.parent = y
#用y替换z,并成为z的双亲的一个孩子,再用z的左孩子替换y的左孩子
self.transplant(cur,y)
y.lchild = cur.lchild
y.lchild.parent = y
if __name__=='__main__':
tree = BinaryTree()
#新元素插入树中
for x in [15,6,18,3,7,17,20,2,4,13,9]:
x = Node(x)
tree.insert(x)
#先序遍历结果
tree.preorder_tree_walk(tree.root)
print('\n')
#中序遍历结果
tree.inorder_tree_walk(tree.root)
print('\n')
#后序遍历结果
tree.postorder_tree_walk(tree.root)
print('\n')
#查找
x = tree.search(tree.root, 15)
if x!=None:
print(x.key)
print("find key in the tree")
else:
print("not find key in the tree")
#查找x后继节点
y = tree.successor(x)
print("x的后继节点关键值为: %s" % str(y.key))
# 最小值
z = tree.minimum(tree.root)
print("最小值 %s" % str(z.key))
#删除节点z,x
tree.delete(z)
tree.delete(x)
#删除节点后的中序遍历结果
tree.inorder_tree_walk(tree.root)
具体代码,可参考github地址:算法导论各章节算法python实现