文章目录
一、二叉查找树(Binary Search Tree)
在ADT Map(抽象数据类型 映射)的实现方案中,可以采用不同的数据结构和搜索算法来保存和查找Key,前面已经实现了两个方案:
- 有序表数据结构+二分搜索算法
- 散列表数据结构+散列及冲突解决算法
下面我们来试试用二叉查找树保存key,实现key的快速搜索。
二叉查找树BST的性质
**比父节点小的key都出现在左子树(是整个左子树,不是子节点),比父节点大的key都出现在右子树。**话句话说,二叉查找树中任意一个节点,左子树的每个节点的值都要小于这个节点的值,而右子树每个节点的值都应大于这个节点的值。
比如,构造一个二叉查找树,按照70,31,93,94,14,23,73的顺序插入:
- 31比70小,放到左子节点
- 93比70大,放到右子节点
- 94比93大,放到右子节点
- 14比31小,放到左子节点
- 23比14大,放到其右
- 73比93小,放到其左
注意:插入顺序不同,生成的BST也不同!
二、二叉查找树的实现(python代码)
节点和链接结构
二叉查找树的实现需要用到BST和TreeNode两个类,BST的root成员引用根节点TreeNode:
class BinarySearchTree:
def __init__(self):
self.root = None
self.size = 0
def length(self):
return self.size
def __len__(self):
return self.size
def __iter__(self):
return self.root.__iter__()
class TreeNode:
def __init__(self, key, val, left=None, right=None, parent=None):
self.key = key # 键
self.payload = val # 值
self.leftChild = left # 左子节点
self.rightChild = right # 右子节点
self.parent = parent # 父节点
def hasLeftChild(self):
"""是否有左子节点"""
return self.leftChild
def hasRightChild(self):
"""是否有右子节点"""
return self.rightChild
def isLeftChild(self):
"""是否是左子节点"""
return self.parent and self.parent.leftChild == self
def isRightChild(self):
"""是否是右子节点"""
return self.parent and self.parent.rightChild == self
def isRoot(self):
"""是否是根节点"""
return not self.parent
def isLeaf(self):
"""是否是叶节点"""
return not (self.rightChild or self.leftChild)
def hasAnyChildren(self):
"""是否拥有子节点"""
return self.rightChild or self.leftChild
def hasBothChildren(self):
"""是否拥有兄弟节点"""
return self.rightChild and self.leftChild
def replaceNodeData(self, key, value, lc, rc):
"""更换当前节点的键值以及左右子节点,并将子节点的父节点指向当前节点"""
self.key = key
self.payload = value
self.leftChild = lc
self.rightChild = rc
if self.hasLeftChild():
self.leftChild.parent = self
if self.hasRightChild():
self.rightChild.parent = self
1. BST.put
方法
put(key, val)
方法:插入key构造BST。
首先看BST是否为空,如果一个节点都没有,那么key成为根节点root;否则,就调用一个递归函数_put(key, val,root)
来放置key。
def put(self, key, val):
# 如果有根节点,就调用_put递归函数
if self.root:
self._put(key, val, self.root)
# 如果没有根节点,就使用当前键值构造根节点
else:
self.root = TreeNode(key, val)
self.size = self.size + 1
def _put(self, key, val, currentNode):
# 如果key比当前节点小,那么放到到左子树
if key < currentNode.key:
# 如果有左子树,就递归进入左子树继续查找合适的空位置
if currentNode.hasLeftChild():
self._put(key, val, currentNode.leftChild)
# 如果没有左子树,那么key就成为左子节点
else:
currentNode.leftChild = TreeNode(key, val, parent=currentNode)
# 如果key比当前节点大,那么放到到右子树
else:
# 如果有右子树,就递归进入右子树继续查找合适的空位置
if currentNode.hasRightChild():
self._put(key, val, currentNode.rightChild)
# 如果没有右子树,那么key就成为右子节点
else:
currentNode.rightChild = TreeNode(key, val, parent=currentNode)
2. BST.__setitem__
方法
在python中,索引赋值(如:my_tree[2]="18"
)是由__setitem__
(前后双下划线)方法实现的,我们这里只需重写即可:
def __setitem__(self,k,v):
self.put(k,v)
3. BST.get
方法
在树中找到key所在的节点取到val值。
def get(self, key):
# 判断是否是空树
if self.root:
# 调用_get递归函数,查找节点
res = self._get(key, self.root)
# 找到则返回值
if res:
return res.val
# 找不到返回None
else:
return None
# 是空树,直接返回None
else:
return None
def _get(self, key, currentNode):
# 如果当前节点不存在,直接返回None
if not currentNode:
return None
# 如果当前节点的key等于要找的key,则返回当前节点
elif currentNode.key == key:
return currentNode
# 如果要找的key小于当前节点的key,进入左子树查找
elif key < currentNode.key:
return self._get(key, currentNode.left)
# 如果要找的key大于当前节点的key,进入右子树查找
else:
return self._get(key, currentNode.right)
4. BST.__getitem__
和BST.__contains__
方法
前者的作用是根据值来获取索引,后者是判断给定索引是否包含在树中,也就是in运算符:
def __getitem__(self,key):
return self.get(key)
def __contains__(self,key):
if self._get(key,self.root):
return True
else:
return False
5. TreeNode类的__iter__
方法(迭代器)
__iter__
方法可以使我们用for循环遍历BST中的所有key。我们的想法是以“左、根、右”的顺序进行遍历,也就是进行中序遍历。
TreeNode类中的__iter__
迭代器用了for循环迭代,而for则会调用__iter__
方法,所以这个方法实际上是个递归函数。
def __iter__(self):
# 基本结束条件:当前节点为空
if self:
# 如果左子树不为空
if self.hasLeftChild():
# 遍历左子树,实质是递归调用
for elem in self.leftChild:
# yield每次都会返回一个elem
yield elem
# 返回根节点
yield self.key
# 如果右子树不为空
if self.hasRightChild():
# 遍历右子树,实质是递归调用
for elem in self.rightChild:
yield elem
6. BST.delete
方法
先用_get
找到要删除的节点,然后调用remove
来删除,找不到则提示错误:
def delete(self, key):
# 剩余元素数量大于1个
if self.size > 1:
# 查找要删除的节点
nodeToRemove = self._get(key, self.root)
# 如果节点存在就调用remove方法删除
if nodeToRemove:
self.remove(nodeToRemove)
self.size -= 1
# 不存在就抛出异常
else:
raise KeyError("Error, key not in tree")
# 剩余元素数量等于1个
elif self.size == 1 and self.root.key == key:
# 删除根节点
self.root = None
self.size -= 1
# 剩余元素数量小于1个,抛出异常
else:
raise KeyError("Erro, key not in tree")
def __delitem__(self, key):
"""实现del myTree[8]这样的索引删除操作"""
self.delete(key)
7. BST.remove
方法
从BST中remove一个节点,还要求仍然保持BST的性质,分以下3种情形:
-
被删节点没有子节点:直接删除;
# 如果是叶节点,说明没有子节点,直接删除 if currentNode.isLeaf(): if currentNode == currentNode.parent.leftChild: currentNode.parent.leftChild = None else: currentNode.parent.rightChild = None
-
被删节点有1个子节点:将这个唯一的子节点上移,替换掉被删节点的位置。
但替换操作需要区分几种情况:
- 被删节点的子节点是左子节点?还是右子节点?
- 被删节点本身是其父节点的左子节点?还是右子节点?
- 被删节点本身就是根节点?
# 否则就是有一个子节点
else:
# 如果被删节点有一个左子节点
if currentNode.hasLeftChild():
# 如果当前节点是父节点的左子节点
if currentNode.isLeftChild():
# 将当前节点的左子节点的父节点设置为当前节点的父节点
currentNode.leftChild.parent = currentNode.parent
# 将当前节点的父节点的左子节点设置为当前节点的左子节点
currentNode.parent.leftChild = currentNode.leftChild
# 如果当前节点是父节点的右子节点,方法与左子节点类似
elif currentNode.isRightChild():
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.leftChild
# 如果当前节点是根节点,直接调用根节点的替换方法
else:
currentNode.replaceNodeData(
currentNode.leftChild.key,
currentNode.leftChild.payload,
currentNode.leftChild.leftChild,
currentNode.leftChild.rightChild,
)
# 如果被删节点有一个右子节点
else:
if currentNode.isLeftChild():
currentNode.rightChild.parent = currentNode.parent
currentNode.parent.leftChild = currentNode.rightChild
elif currentNode.isRightChild():
currentNode.rightChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.rightChild
else:
currentNode.replaceNodeData(
currentNode.rightChild.key,
currentNode.rightChild.payload,
currentNode.rightChild.leftChild,
currentNode.rightChild.rightChild,
)
- 被删节点有2个子节点:选择被删节点右子树所有节点中最小的那个作为“后继”,替换掉被删节点的位置,然后再去调整。由于二叉查找树的性质,“后继”要么本身是叶节点,要么只有1个右子节点。所以只要把原先的“后继”摘除掉就可以了。
elif currentNode.hasBothChildren():
# 寻找“后继”
succ = currentNode.findSuccessor()
# 将“后继”摘除
succ.spliceOut()
# 用“后继”替换当前的节点
currentNode.key = succ.key
currentNode.payload = succ.payload
TreeNode类:寻找“后继”节点
def findSuccessor(self):
succ = None
# 如果当前节点有右子节点,就调用findMin方法
if self.hasRightChild():
succ = self.rightChild.findMin()
# 下面的情况暂时用不到
else:
if self.parent:
if self.isLeftChild():
succ = self.parent
else:
self.parent.rightChild = None
succ = self.parent.findSuccessor()
self.parent.rightChild = self
return succ
def findMin(self):
current = self
# 如果有左子树,就进入左子树继续寻找
while current.hasLeftChild():
current = current.leftChild
return current
def spliceOut(self):
if self.isLeaf():
# 如果是叶节点,直接“摘除”
if self.isLeftChild():
self.parent.leftChild = None
# 下面else的情况暂时用不到
else:
self.parent.rightChild = None
elif self.hasAnyChildren():
# 下面if的情况暂时用不到
if self.hasLeftChild():
if self.isLeftChild():
self.parent.leftChild = self.leftChild
else:
self.parent.rightChild = self.leftChild
self.leftChild.parent = self.parent
# 如果有一个右子节点,“摘除”
else:
if self.isLeftChild():
self.parent.leftChild = self.rightChild
# 下面else的情况暂时用不到
else:
self.parent.rightChild = self.rightChild
self.rightChild.parent = self.parent
8. 完整代码
class TreeNode:
def __init__(self, key, val, left=None, right=None, parent=None):
self.key = key
self.payload = val
self.leftChild = left
self.rightChild = right
self.parent = parent
def hasLeftChild(self):
return self.leftChild
def hasRightChild(self):
return self.rightChild
def isLeftChild(self):
return self.parent and self.parent.leftChild == self
def isRightChild(self):
return self.parent and self.parent.rightChild == self
def isRoot(self):
return not self.parent
def isLeaf(self):
return not (self.rightChild or self.leftChild)
def hasAnyChildren(self):
return self.rightChild or self.leftChild
def hasBothChildren(self):
return self.rightChild and self.leftChild
def spliceOut(self):
if self.isLeaf():
if self.isLeftChild():
self.parent.leftChild = None
else:
self.parent.rightChild = None
elif self.hasAnyChildren():
if self.hasLeftChild():
if self.isLeftChild():
self.parent.leftChild = self.leftChild
else:
self.parent.rightChild = self.leftChild
self.leftChild.parent = self.parent
else:
if self.isLeftChild():
self.parent.leftChild = self.rightChild
else:
self.parent.rightChild = self.rightChild
self.rightChild.parent = self.parent
def findSuccessor(self):
succ = None
if self.hasRightChild():
succ = self.rightChild.findMin()
else:
if self.parent:
if self.isLeftChild():
succ = self.parent
else:
self.parent.rightChild = None
succ = self.parent.findSuccessor()
self.parent.rightChild = self
return succ
def findMin(self):
current = self
while current.hasLeftChild():
current = current.leftChild
return current
def replaceNodeData(self, key, value, lc, rc):
self.key = key
self.payload = value
self.leftChild = lc
self.rightChild = rc
if self.hasLeftChild():
self.leftChild.parent = self
if self.hasRightChild():
self.rightChild.parent = self
class BinarySearchTree:
def __init__(self):
self.root = None
self.size = 0
def length(self):
return self.size
def __len__(self):
return self.size
def put(self, key, val):
if self.root:
self._put(key, val, self.root)
else:
self.root = TreeNode(key, val)
self.size = self.size + 1
def _put(self, key, val, currentNode):
if key < currentNode.key:
if currentNode.hasLeftChild():
self._put(key, val, currentNode.leftChild)
else:
currentNode.leftChild = TreeNode(key, val, parent=currentNode)
else:
if currentNode.hasRightChild():
self._put(key, val, currentNode.rightChild)
else:
currentNode.rightChild = TreeNode(key, val, parent=currentNode)
def __setitem__(self, k, v):
self.put(k, v)
def get(self, key):
if self.root:
res = self._get(key, self.root)
if res:
return res.payload
else:
return None
else:
return None
def _get(self, key, currentNode):
if not currentNode:
return None
elif currentNode.key == key:
return currentNode
elif key < currentNode.key:
return self._get(key, currentNode.leftChild)
else:
return self._get(key, currentNode.rightChild)
def __getitem__(self, key):
return self.get(key)
def __contains__(self, key):
if self._get(key, self.root):
return True
else:
return False
def delete(self, key):
if self.size > 1:
nodeToRemove = self._get(key, self.root)
if nodeToRemove:
self.remove(nodeToRemove)
self.size = self.size - 1
else:
raise KeyError("Error, key not in tree")
elif self.size == 1 and self.root.key == key:
self.root = None
self.size = self.size - 1
else:
raise KeyError("Error, key not in tree")
def __delitem__(self, key):
self.delete(key)
def remove(self, currentNode):
if currentNode.isLeaf(): # leaf
if currentNode == currentNode.parent.leftChild:
currentNode.parent.leftChild = None
else:
currentNode.parent.rightChild = None
elif currentNode.hasBothChildren(): # interior
succ = currentNode.findSuccessor()
succ.spliceOut()
currentNode.key = succ.key
currentNode.payload = succ.payload
else: # this node has one child
if currentNode.hasLeftChild():
if currentNode.isLeftChild():
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.leftChild = currentNode.leftChild
elif currentNode.isRightChild():
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.leftChild
else:
currentNode.replaceNodeData(
currentNode.leftChild.key,
currentNode.leftChild.payload,
currentNode.leftChild.leftChild,
currentNode.leftChild.rightChild,
)
else:
if currentNode.isLeftChild():
currentNode.rightChild.parent = currentNode.parent
currentNode.parent.leftChild = currentNode.rightChild
elif currentNode.isRightChild():
currentNode.rightChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.rightChild
else:
currentNode.replaceNodeData(
currentNode.rightChild.key,
currentNode.rightChild.payload,
currentNode.rightChild.leftChild,
currentNode.rightChild.rightChild,
)
三、算法分析(以put方法为例)
其性能决定因素在于二叉搜索树的高度(最大层次),而其高度又受数据项key
插入顺序的影响。
如果key的列表是随机分布的话,那么大于和小于根节点key的键值大致相等,BST的高度就是 log 2 n \log _{2}n log2n(n是节点的个数),而且,这样的树就是平衡树。put方法最差性能为 O ( log 2 n ) O(\log _2n) O(log2n)。
但key列表分布极端情况就完全不同,按照从小到大顺序插入的话,就会如下图:
此时,put方法的性能为 O ( n ) O(n) O(n),其他方法的情况也是类似的。