二叉查找树(Binary Search Tree)
, 简称BST
,必须具有以下性质:
- 若任意节点的左子树不空,则左子树上所有节点的值均
小于
它的根结点的值 - 若任意节点的右子树不空,则右子树上所有节点的值均
大于
它的根结点的值 - 任意节点的左、右子树也分别为二叉查找树
- 没有键值相等的节点
在二叉查找树中查找节点时, 平均运行时间为O(logn)
(平衡情况), 最坏为O(n)
(极度不平衡), 平均深度是O(logn)
在有序数组中使用二分查找时最坏的时间复杂度是O(logn)
, 但是二叉搜索树的插入
和删除
的性能更优
二叉搜索树和数组对比
- | 数组 | 二叉搜索树 |
---|---|---|
查找 | O(logn)(二分查找) | O(logn) |
插入 | O(n) | O(logn) |
删除 | O(n) | O(logn) |
实现
- 插入
- 查找
- 删除
- 要删除的节点是叶子节点, 只需要让父节点指向null
- 要删除的节点是只有一个子节点(不管是左/右), 需要让父节点指向子节点
- 要删除的节点有两个子节点, 则需要找到该节点右子树中的最小节点, 然后替换到该节点, 并且再删除这个最小节点
特点
- 快速的
插入/查找/删除
- 快速找到
最大/最小
节点 - 中序遍历可以得到有序的数据, O(n)
# coding:utf-8
class TreeNode(object):
def __init__(self, key, value, parent=None, left=None, right=None):
self.key = key
self.value = value
self.parent = parent
self.left = left
self.right = right
def isLeftChild(self):
return self.parent and self.parent.left == self
def isRightChild(self):
return self.parent and self.parent.right == self
def childrenNums(self):
num = 0
if self.left:
num += 1
if self.right:
num += 1
return num
class BinarySearchTree(object):
def __init__(self):
self.root = None
self.size = 0
def put(self, key, value):
if self.root:
self._put(key, value, self.root)
else:
self.root = TreeNode(key, value)
self.size += 1
def _put(self, key, value, current):
if key < current.key:
if current.left:
self._put(key, value, current.left)
else:
current.left = TreeNode(key, value, parent=current)
elif key > current.key:
if current.right:
self._put(key, value, current.right)
else:
current.right = TreeNode(key, value, parent=current)
def get(self, key):
if self.root:
res = self._get(key, self.root)
else:
return None
return res
def _get(self, key, current):
if not current:
return None
if key < current.key:
return self._get(key, current.left)
elif key > current.key:
return self._get(key, current.right)
else:
return current
def delete(self, key):
if self.size > 1:
# 要先找到该节点
node2remove = self.get(key)
if node2remove:
self.remove(node2remove)
self.size -= 1
else:
raise Exception('no element')
elif self.size == 1 and self.root.key == key:
self.root = None
self.size -= 1
else:
raise Exception('no element')
def remove(self, current):
childrens = current.childrenNums()
if 0 == childrens:
if current.isLeftChild():
current.parent.left = None
else:
current.parent.right = None
elif 1 == childrens:
# 如果该节点有左子树
if current.left:
if current.isLeftChild():
current.left.parent = current.parent
current.parent.left = current.left
elif current.isRightChild():
current.left.parent = current.parent
current.parent.right = current.left
else:
self.root = current.left
# 如果是右子树
elif current.right:
if current.isLeftChild():
current.right.parent = current.parent
current.parent.left = current.right
elif current.isRightChild():
current.right.parent = current.parent
current.parent.right = current.right
else:
self.root = current.right
# 如果有两个子节点
else:
parent = current
minChild = current.right
while minChild.left != None:
parent = minChild
minChild = minChild.left
current.key = minChild.key
current.value = minChild.value
# 注意以下情况判断, 因为有的没有左子节点
if parent.left == minChild:
parent.left = minChild.right
if minChild.right:
minChild.right.parent = parent
else:
parent.right = minChild.right
if minChild.right:
minChild.right.parent = parent
def length(self):
return self.size
def __setitem__(self, key, value):
self.put(key, value)
def __getitem__(self, key):
return self.get(key)
def __delitem__(self, key):
self.delete(key)
def mid(self, root):
if not root:
return
self.mid(root.left)
print(root.value)
self.mid(root.right)
if __name__ == '__main__':
mytree = BinarySearchTree()
mytree[7] = "7"
mytree[2] = "2"
mytree[11] = "11"
mytree[8] = "8"
mytree[19] = "19"
mytree[5] = "5"
mytree[1] = "1"
# 中序遍历
mytree.mid(mytree.root)
print('------')
# 删除叶子节点1
del mytree[1]
mytree.mid(mytree.root)
print('------')
# 删除有一个子节点的2
del mytree[2]
mytree.mid(mytree.root)
print('------')
# 删除有两个子节点的11
del mytree[11]
mytree.mid(mytree.root)
时间复杂度
- 最坏: 退化成链表 O(n)
- 最好: 平衡 O(logn)
问题
散列表更加高效, 为什么还有二叉查找树?
- 散列表的数据时无序存储的, 如果要输出有序数据的话还需要额外进行排序. 但是二叉查找树可以直接中序遍历(O(n))
- 散列表扩容耗时长, 遇到散列冲突时性能不稳定. 工程中用的AVL树性能稳定
- 散列表构造复杂, 需要考虑散列函数, 冲突解决等. 而二叉查找树仅仅考虑平衡问题
资料
- <<大话数据结构>>
- 二叉树
- <<数据结构和算法>>