一、二叉搜索树的特征
1.二叉搜索树与二叉树
所谓二叉搜索树,其实本质上就是一个树状的数据结构,也是有一个根节点和若干个子树所组成的,其基于二叉树的结构,而在其数据的填充位置上所进行优化而得到的数据结构,并运用二叉搜索树数据的分布特点来实现在搜索功能。二叉树的结构特征如下图:
2.二分查找
基于二叉搜索树每个节点中的数值大小均满足:左子节点的值 < 根节点的值 < 右子节点的值的特点,二叉搜索树在搜索值时是依照二分来进行查找的,即先将待查找的值与每棵子树的根节点的值进行比较,当待查找的值小于每棵子树的根节点的值时,到该根节点的左子树递归查找;当待查找的值大于每棵子树的根节点的值时,到该根节点的右子树递归查找;待查找的值等于每棵子树的根节点的值时,即找到待查找的值所在的节点。除此之外,在二叉搜索树进行删除节点,添加节点,修改节点等操作时也会使用二分查找的思想。相比较于链表而言,二叉搜索树在搜索查找方面的效率要提高不少。
3.递归
二叉树的许多相关操作是和递归密不可分的。从上述二叉树的结构图中,我们不难看出,二叉树是由若干个由一个根节点和左右两个叶子节点构成的子树所组成的;而每棵子树的根节点可能是其上级子树的叶子节点,而叶子节点也可能是其下级子树的根节点,这也就表明了递归在二叉树结构中的使用。也正是由于二叉搜索树与二叉树相同的结构特点,使得二叉搜索树在许多相关操作和相关问题的解决上均会运用到递归这种实现。
二、二叉搜索树相关操作的实现
1.添加操作
二叉搜索树增加节点的操作和链表的增加操作相类似,均是通过增加节点中指针的指向,从而来添加新的节点元素。与链表不同的是,二叉搜索树在执行增加节点的操作时会遵守 左子节点的值 < 根节点的值 < 右子节点的值 的结构特征,将待添加的值同树根节点的值比较大小,并更加大小的比较情况,递归地将待添加值与左右子树的节点比较,以此来查找到添加的位置,这也是二分查找思想的运用。
代码实现:
#添加节点
def add_node(self, val):
def recurse_node(node): #node为当前节点
if val < node.val:
if not node.lchild: #当待添加的值小于当前节点的值,且该节点没有左子树时
node.lchild = Node(val) #将待添加的节点添加到当前节点的左子节点上
else: #当待添加的值小于当前节点的值,且该节点有左子树时
recurse_node(node.lchild) #向当前节点的左子树递归添加节点
elif not node.rchild: #当待添加的值大于当前节点的值,且该节点没有右子树时
node.rchild = Node(val) #将待添加的节点添加到当前节点的右子节点上
else: #当待添加的值大于当前节点的值,且该节点有右子树时
recurse_node(node.rchild) #向当前节点的右子树递归添加节点
时间复杂度:
最坏情况:当树上的节点全都集中在根节点的某一侧时,二叉树在上就会成为一个单链结构,即添加操作的时间复杂度将会为 O(n) ,其中 n 取决于二叉树上节点的个数。
一般情况:根据二叉树就结构的特征,其在添加操作上的时间复杂度和二分查找算法的时间复杂度相类似,即添加操作的平均时间复杂度为:O(log n)
2.查找操作
同上文所述,基于二叉搜索树 左子节点的值 < 根节点的值 < 右子节点的值 的结构特征,即可通过二分查找的思想来查找我们所要查找的元素节点,即先将待查找的值与每棵子树的根节点的值进行比较,当待查找的值小于每棵子树的根节点的值时,到该根节点的左子树递归查找;当待查找的值大于每棵子树的根节点的值时,到该根节点的右子树递归查找;待查找的值等于每棵子树的根节点的值时,即找到待查找的值所在的节点。
代码实现:
#二叉搜索树的查找
def BST_Search(self, root, val): #要求将要查找的值val封装为树节点root,在传入函数中
if not root: #当该二叉搜索树为空时,直接返回None
return None
if val == root.val: #当节点的值等于要查找的值时,返回该节点
return root
elif val < root.val: #当要查找的值小于当前节点的值时,在当前节点的左孩子递归查找
return self.BST_Seach(root.lchild, val)
else: #当要查找的值大于当前节点的值时,在当前节点的右孩子递归查找
return self.BST_Seach(root.rchild, val)
时间复杂度:
最坏情况:当二叉搜索树的所有节点都集中在根节点的一侧式,这棵树就会成为一个单链结构,故查找的最坏时间复杂度为:O(n) (其中 n 取决于树中节点的个数)
一般情况:二叉搜索树的查找是基于二分查找的思想实现的,故平均的时间复杂度为O(log n)
3.删除操作
二叉搜索树的删除操作会有点复杂,这体现在,当我们找到待删除的节点时,需要对该节点的不同的情况进行讨论。
情况1:当待删除的节点没有左子节点时,则用该待删除节点的右子节点来代替删除的节点的位置
情况2:当待删除的节点没有右子节点时,则用该待删除节点的左子节点来代替删除的节点的位置
情况3:当待删除节点的左右子节点均存在时,基于二叉搜索树 左子节点的值 < 根节点的值 < 右子节点的值 的特点,我们先将一个 cur 指针指向待删除节点的右子节点,并使 cur 指针不断指向该右子节点的左子节点,当 cur 指针所指向的节点没有左子节点时,将删除节点的左子树全部添加到 cur 指针所指向的节点的左子节点。
代码实现:
#二叉搜索树的删除
def BST_delete(self, root, val):
if not root:
return root
if root.val > val: #当所要删除的节点的值小于根节点的值时
root.lchild = self.BST_delete(root.lchild, val) #在根节点的左侧递归查找要删除的节点,并将其删除
return root
elif root.val < val: #当要删除的节点的值大于根节点在值时
root.rchild = self.BST_delete(root.rchild, val) #在根节点的右侧递归查找要删除的节点,并将其删除
return root
else: #当要删除的节点等于根节点的值时,有以下三种情况:
if not root.lchild: #(1)当待删除的节点没有左孩子时:
return root.rchild #用删除节点的右孩子代替删除节点的位置
elif not root.rchild: #(2)当待删除节点没有右孩子时
return root.lchild #用删除节点的左孩子代替删除节点的位置
else: #(3)当待删除节点的左右孩子都存在时
cur = root.rchild #先将cur指针指向待删除节点的右孩子
while cur.lchild:
cur = cur.lchild #用循环不断将cur指针指向它的左孩子
cur.lchild = root.lchild #当cur指针所指的节点没有左孩子时,将删除节点的左孩子全部添加到cur所指的节点的左孩子
return root.rchild
时间复杂度:
与上述的操作相类似。
最坏情况:当树上的节点均集中在其根节点的用一侧时,这棵树将会形成一个单链的结构,故此时删除操作的时间复杂度为:O(n),其中 n 取决于树中节点的个数。
一般情况:在二叉搜索树进行删除操作时,会优先查找要进行删除的节点,故删除操作的时间复杂度也为 O(log n)
4.插入操作
二叉搜索树即是根据 左子节点的值 < 根节点的值 < 右子节点的值 的规则,将数据插入到树中符合条件的节点上。运用二分查找和递归的思想,当待插入的值小于当前节点的值时,在当前节点的左孩子递归插入;当待插入的节点大于当前节点的值时,在当前节点的右孩子递归插入。
代码实现:
#二叉搜索树的插入
def BST_Insert(self, root, val):
if root == None: #当当前的二叉搜索树为空时
return Node(val) #直接返回插入的节点
if val < root.val: #当待插入的节点小于当前节点的值时
root.lchild = self.BST_Insert(root.lchild, val) #在当前节点的左孩子递归插入
if val > root.val: #当待插入的节点大于当前节点的值时
root.rchild = self.BST_Insert(root.rchild, val) #在当前节点的右孩子递归插入
return root
时间复杂度:
最坏情况:当所有的节点全部都集中在树根节点的一侧时,这棵树就会形成单链的结构,此时插入操作的时间复杂度为:O(n),其中 n 取决于树上节点的个数。
一般情况:于上述的操作相类似,运用二分查找的思想,即二叉搜索树插入操作的一般时间复杂度为:O(log n)
5.创建二叉搜索树
创建二叉搜索树,即是将传入的一组数据按照 左子节点的值 < 根节点的值 < 右子节点的值 的规则包装成树状的结构。这个过程可以看成是将传入的数据先封装成节点,再将封装后的节点插入树中。
代码实现:
#二叉搜索树的创建
def BST_Build(self, nums): #传入要建立二叉树的值
root = self.val #将传入的值封装成树的节点
for num in nums: #运用二叉搜索树的插入函数循环将值插入指定的树节点上
self.BST_Insert(root, num)
return root
三、二叉搜索树的完整Python代码
class Node:
def __init__(self, val): #定义树节点
self.val = val
self.lchild = None #建立左子树
self.rchild = None #建立右子树
#定义二叉搜索树
class BST:
def __init__(self, root = None):
self.root = None
self.size = 0
#二叉搜索树判空
def is_empty(self):
return self.root == None
#统计二叉搜索树的节点个数
def size(self):
return self.size
#清空二叉搜索树
def clear(self):
self.root = None
#添加节点
def add_node(self, val):
def recurse_node(node):
if val < node.val:
if not node.lchild:
node.lchild = Node(val)
else:
recurse_node(node.lchild)
elif not node.rchild:
node.rchild = Node(val)
else:
recurse_node(node.rchild)
if self.is_empty == 0:
self.root = Node(val)
else:
recurse_node(self.root)
self.size += 1
#二叉搜索树的查找
def BST_Search(self, root, val):
if not root:
return None
if val == root.val:
return root
elif val < root.val:
return self.BST_Seach(root.lchild, val)
else:
return self.BST_Seach(root.rchild, val)
#二叉搜索树的插入
def BST_Insert(self, root, val):
if root == None:
return Node(val)
if val < root.val:
root.lchild = self.BST_Insert(root.lchild, val)
if val > root.val:
root.rchild = self.BST_Insert(root.rchild, val)
return root
#二叉搜索树的创建
def BST_Build(self, nums):
root = self.val
for num in nums:
self.BST_Insert(root, num)
return root
#二叉搜索树的删除
def BST_delete(self, root, val):
if not root:
return root
if root.val > val:
root.lchild = self.BST_delete(root.lchild, val)
return root
elif root.val < val:
root.rchild = self.BST_delete(root.rchild, val)
return root
else:
if not root.lchild:
return root.rchild
elif not root.rchild:
return root.lchild
else:
cur = root.rchild
while cur.lchild:
cur = cur.lchild
cur.lchild = root.lchild
return root.rchild
四、总结
二叉搜索树实质上是建立在二叉树上,具有搜索功能的数据结构。相比较于数组和链表,二叉搜索树运用了二分查找的算法思想,将对多个数据进行相关操作的时间复杂度控制在 O(log n),提高了对大规模数据进行操作的时间效率。