二叉树-二叉搜索树
文章目录
时间线
- 2020年7月9日-完成初稿
参考链接
上面是希望通过生成器和递归结合,获得遍历结果
前言
之所以选择二叉搜索树作为二叉树模块的起点,是因为对于二叉搜索树还是有点印象的,
但是弄懂二叉搜索树的相关内容,还是花费了一天的时间。
其中最大的时间占比是代码实现部分,因为不断地推翻与重建,最后才写出还算满意的内容;
当然代码实现只是知识的应用,所以在代码实现的过程中,也弄懂了一些细节的知识。
例如当插入二叉搜索树中已有的元素时,应该如何处理等等。
定义
定义
二叉搜索树(Binary Search Tree,简写BST)是二叉树的一种,也是一种经典的数据结构。
顾名思义,其主要用途是搜索,常用于文件系统和数据库系统的排序和检索
性质
- 一个节点的左子树只能包含值比该节点小的节点
- 一个节点的右子树只能包含值比该节点大的节点
- 以每个节点为根节点的树都是二叉搜索树
一图流
二叉搜索树的构造
根据元素的插入顺序构造的二叉搜索树
可以看到作为根节点的49,是数据集合的第一个元素
根节点的左子树的各个节点值都小于根节点值49,根节点的右子树的各个节点值都大于根节点值49
二叉搜索树无唯一
具有相同元素的数据集合,元素的顺序不同,根据元素顺序而构造的二叉搜索树也不唯一,如下:
数据集合内有共同元素49,38,65,但顺序不同,而构造出的二叉搜索树也不同
二叉搜索树元素不可重复
当元素出现重复时,将不符合二叉搜索树的定义
例如上图,出现重复元素49,则违反定义 根节点右子树的节点值大于根节点的值
逻辑
无论是数据库还是数据结构,都逃不过4种主要操作行为:插入、删除、修改、查询(CRUD)
所以二叉搜索树主要有以下操作:
插入:插入元素到二叉搜索树
相对简单,根据元素的值插入到二叉搜索树中
定义插入元素为待插入元素
从根节点出发
当根节点的值为空时,直接将根节点的值设置为带插入元素的值
当根节点的值不为空时,取根节点的值与待插入元素的值进行比较
当根节点的值等于待插入元素的值时,不做任何操作
当根节点的值大于待插入元素的值时,说明待插入元素应该在根节点的左子树,
则根节点下移到根节点的左子节点
当根节点的值小于待插入元素的值时,说明待插入元素应该在根节点的右子树,
则根节点下移到根节点的右子节点
重复以上步骤,直至完成插入
删除:删除某个元素
相对复杂,见逻辑思考-节点的状态与相关节点的位置关系是删除二叉树节点的关键
修改:修改某个值
二叉搜索树的主要用途是搜索,所以不加记录
如果实现的话,可以猜想:整个二叉搜索树的结构会发生非常大的变化,而不是简单地替换
查询:最小元素/最大元素
相对简单,
在二叉搜索树中,最小元素节点即使最左下节点,也即中序遍历的“后继节点”
而最大元素即最右下节点,也即中序遍历的的“前驱节点”
(这里圈起来是因为需要为整个二叉搜索树增加一个假设值Null的“双亲节点”,如下)
逻辑思考
-
节点的状态与相关节点的位置关系是删除二叉树节点的关键
定义需要删除的节点为待删除节点,待删除节点的双亲节点为双亲节点
待删除节点的子节点为子节点
-
无子节点:节点无子节点,则表示该节点是叶子节点,
可以直接删除
-
只有一个子节点:这个子节点的位置非常关键
-
待删除节点是双亲节点的左子节点:将子节点设置为双亲节点的左子节点
-
待删除节点是双亲节点的右子节点:将子节点设置为双亲节点的右子节点
-
-
有两个子节点
-
找到以右子节点的根节点的二叉搜索树的最小值节点,
即待删除节点的后继节点
-
将该后继节点的值设置为待删除节点的值
-
删除后继节点
后继节点的删除也是节点删除中的一种,但不会是由两个子节点的。
因为后继节点是待删除节点的右子树中最小的一个数,那么说明:
后继节点只有两种可能,无子节点或者只有一个右子节点
-
-
时间复杂度
实现
Python
注意:测试中的main函数内的代码可以直接粘贴到源文件中,就不会出现导入问题
源文件
# encoding:utf-8
"""
@author:zhj1121
@time:2020.7.9
@filename:mybst.py
"""
class Node(object):
def __init__(self,data):
self.data = data
self.leftChild = None
self.rightChild = None
class BST(object):
def __init__(self,root = None):
self.root = root
def insert(self,data):
if not self.root:
self.root = Node(data)
return True
else:
flag,currentNode,parent,around= self.search(self.root,self.root,data)
""" 不存在则可插入 """
if not flag:
newNode = Node(data)
if parent.data > data:
parent.leftChild = newNode
else:
parent.rightChild = newNode
return True
else:
return False
def insertList(self,eleList):
try:
for i in eleList:
self.insert(i)
return True
except:
return Exception("exception")
def search(self,node,parent,data,around=None):
""" 返回值:flag,当前节点,双亲节点 """
if not node:
#node节点的双亲节点是None
return False,node,parent,around
else:
if node.data == data:
return True,node,parent,around
elif node.data > data:
return self.search(node.leftChild,node,data,0)
else:
return self.search(node.rightChild,node,data,1)
def getMinByNode(self,node,parent):
""" 寻找以node为根节点的的树最小节点
这个最小节点是是parent的中序遍历的后继节点
查找整棵树的最小节点,
可以:node = self.root
查找根节点的后继节点,
可以:node = self.root.rightChild
return: node,parent
"""
#当前节点为空
if not node:
return None,parent
#当前节点无左孩子,则表示当前节点是最小节点
elif not node.leftChild:
return node,parent
#否则,则递归当前节点的左孩子
else:
return self.getMinByNode(node.leftChild,node)
def getMindata(self):
minnode,parent = self.getMinByNode(self.root,self.root)
if not minnode:
return parent.data
else:
return minnode.data
def getMaxByNode(self,node,parent):
""" 寻找以node为根节点的树中最大节点
这个最大节点是以parent的中序遍历的前驱节点
return : node,parent
"""
if not node:
return None,parent
elif not node.rightChild:
return node,parent
else:
return self.getMaxByNode(node.rightChild,node)
def getMaxdata(self):
maxnode,parent = self.getMaxByNode(self.root,self.root)
if not maxnode:
return parent.data
else:
return maxnode.data
def delNodeBydata(self,root,data):
flag,node,parent,around = self.search(root,root,data)
#二叉搜索树没有对象data
if not flag:
return Exception("不存在该对象")
#否则
#flag,node,parent : True,待删除节点,待删除节点的双亲节点
else:
#待删除节点 : 无左孩子
if not node.leftChild:
#around:获得node与parent的位置关系
#注意:
# 这里还有一个逻辑,即node节点是无左右孩子的
#这时,也是执行if逻辑,并不会冲突
#因为node.rightChild = None
#parent.rightChild = None
#所以,是正确的
#parent.rightChild = node.rightChild
if around:#node在parent的右边
parent.rightChild = node.rightChild
else:#node在parent的左边
parent.leftChild = node.rightChild
# 待删除节点 : 无右孩子
elif not node.rightChild:
if around:
parent.rightChild = node.leftChild
else:
parent.leftChild = node.rightChild
# 待删除节点 : 有左右孩子
else:
# #找到待删除节点的后继节点,即待删除节点的右子树中,最小的节点
# current,current_parent = self.getMinByNode(node.rightChild,node)
# #无左孩子
# if not current:
# node.data = current_parent.data
# 找到待删除节点的后继节点,即待删除节点的右子树中,最小的节点
preNode = node.rightChild#获得右子树的根节点
#右子树没有左孩子,则根节点是最小节点
if not preNode.leftChild:
node.data = preNode.data
node.rightChild = preNode.rightChild
del preNode
else:
#往下遍历
nextNode = preNode.leftChild
while nextNode.leftChild:
preNode = nextNode
nextNode = nextNode.leftChild
#nextNode将会是后继节点
node.data = nextNode.data
preNode.leftChild = nextNode.rightChild
def preOrderTraversal(self,node):
""" 先序遍历 """
if node:
yield node.data
for i in self.preOrderTraversal(node.leftChild):
yield i
for i in self.preOrderTraversal(node.rightChild):
yield i
def inOrderTraversal(self,node):
""" 中序遍历 """
if node:
for i in self.inOrderTraversal(node.leftChild):
yield i
yield node.data
for i in self.inOrderTraversal(node.rightChild):
yield i
def postOrderTraversal(self,node):
""" 后序遍历 """
if node:
for i in self.postOrderTraversal(node.leftChild):
yield i
for i in self.postOrderTraversal(node.rightChild):
yield i
yield node.data
def getInorderTraversalList(self,node):
result = []
yielder = self.inOrderTraversal(node)
for i in yielder:
result.append(i)
return result
def getTraversalList(self,node):
""" 先序/中序/后序遍历 """
preList = [_ for _ in self.preOrderTraversal(node)]
inList = [_ for _ in self.inOrderTraversal(node)]
postList = [_ for _ in self.postOrderTraversal(node)]
return preList,inList,postList
测试
from binarySortTree.mybst import BST
if __name__ == "__main__":
ls = [49,38,65,97,60,76,13,27,5,1,1]
bst = BST()
# """ 插入 """
print("插入节点:")
error = []
for i in ls:
if bst.insert(i):
print("{}".format(i),end=' ')
else:
error.append(i)
print("\n插入完成,其中{}插入失败,共{}次".format(error,len(error)))
# or bst.insertList(ls)
# """ 获取根节点数据 """
print('根节点:{}'.format(bst.root.data))
# """ 获取先/中/后序遍历 """
prelist,inlist,postlist = bst.getTraversalList(bst.root)
print(
"先序遍历:{}\n中序遍历:{}\n后序遍历:{}".format(
prelist,inlist,postlist
)
)
# """ 删除节点49 """
delELement = 49
print("删除节点{}".format(delELement))
bst.delNodeBydata(bst.root,delELement)
# """ 获取中序遍历结果 """
print("中序遍历结果:{}".format(bst.getInorderTraversalList(bst.root)))
# """ 获取二叉搜索树中的最大值/最小值 """
print(
"{}中的最大值为{},最小值为{}".format(bst.getInorderTraversalList(bst.root),
bst.getMaxdata(),bst.getMindata())
)
运行结果
思考
二叉树都有一个特点,就是"树模式"——每一个节点都可以作为一棵二叉树的根节点
插入元素的过程,就是在树上增加分支的过程,每一个元素都有一个属于自己的位置
而影响的是元素的插入顺序。
删除过程,需要考虑节点的状态以及相对位置
查询,也即遍历过程,由于"树模式",所以可以使用递归