1.二叉树
二叉树(Binary Tree)是一种数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。节点中包含一个值和指向左右子节点的指针。如图
1.1二叉树的链式存储
将而二叉树的节点定义为一个对象,节点之间通过类似链表的方式来连接。
1.1.1 节点定义
# 二叉树
# 节点定义
class BiTreeNode:
def __init__(self, data):
self.data = data
self.rchild = None # 右孩子
self.lchild = None # 左孩子
1.1.2插入数据
为了介绍后面的二叉树的遍历,我们插入上图中的几个数据。
a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")
# 如图对应各个节点对应相应的左右孩子
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e # 根节点
1.2 二叉树的遍历
1.2.1前序遍历
# 前序遍历
def pre_order(root):
if root: # 如果有数据
print(root.data, end=' ')
pre_order(root.lchild)
pre_order(root.rchild)
# 输出前序遍历
pre_order(root)
输出结果:
如何理解
借助图来理解,在前序遍历上述二叉树的时候,使用递归的方式来进行代码实现。在递归过程中,先打印根节点E,再递归左子树(先递归)和右子树(后递归),即上图的中的两个黑色的框,在左边黑色框里,先打印E的左子树中的节点A,然后再递归A的左右子树,在递归到A的右子树时(蓝色框),同样先打印节点C,再递归C的左右子树,所以C后面会打印B和D。同理右边的黑色框,会先打印G然后再递归G的子树,接着打印F,所以整体打印出来就是EACBDGF。
1.2.2中序遍历
# 中序遍历
def in_order(root):
if root:
in_order(root.lchild)
print(root.data, end=' ')
in_order(root.rchild)
# 打印中序遍历
in_order(root)
输出结果:
如何理解
在中序遍历上述二叉树的时候,先递归E的左子树(左黑色框),再打印根节点,再递归E的右子树(右黑色框),再递归E的左子树时,同样先递归A的左子树,再打印A,再递归A的右子树,由于A没有左子树,所以打印A之后递归右子树(绿色框),在递归A的右子树时同理,先递归C的左子树(打印B),再打印C,在递归C的右子树(打印D)。在递归完E的左子树后,打印E,再递归E的右子树,同理,先递归左子树,再打印G,再递归右子树。
1.2.3后序遍历
# 后序遍历
def post_order(root):
if root:
post_order(root.lchild)
post_order(root.rchild)
print(root.data, end=' ')
# 打印后序遍历
post_order(root)
输出结果:
如何理解
在看懂前面两种遍历之后,后序遍历就很好懂了,先递归E的左子树(左边黑色框),再递归E的右子树(右边黑色框),再打印E。在递归E的左子树的时候,同样先递归A的左子树(图中没有)再递归A的右子树,再打印A,同理在递归A的右子树时,也是先左子树(B)后右子树(D)最后打印A,在E的左子树递归完成以后,再看E的右子树,同理,先打印F,再打印G,当E的左右子树都递归完成以后,最后打印E。
1.2.4三种遍历方式的联系
在前序遍历中第一个元素E是根节点,对应到中序遍历,E的两边分别是E的左子树和右子树的元素,将左右子树的元素对应到前序遍历中又可以得到子树中的根节点(前序遍历中子树元素中的第一个元素对应的该子树的根节点)。
在后序遍历中最后一个元素E是根节点,再对应到中序遍历,可以得到E的左右子树中的元素,再将左右子树中的元素对应到后序遍历中,在后序遍历中子树元素中的最后的一个元素是该子树的根节点。
2.二叉搜索树
二叉搜索树(Binary Search Tree,也称为二叉排序树或二叉查找树)满足以下条件:
-
对于任意节点N,其左子树中的所有节点的值均小于N的值,右子树中的所有节点的值均大于N的值。
-
左、右子树也必须分别是二叉搜索树。
如图例:
2.1二叉搜索树的实现
2.1.1节点定义
# 节点定义
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子
self.rchild = None # 右孩子
self.parent = None # 父节点
2.1.2元素插入
关于元素的插入,由于二叉搜索树的特点,以上面的图示为例,比如说插入的元素是10,10比17小,要放在17的左子树,10比5大,要放在5的右子树,10比11小,要放在11的左子树,10比9大,要放在9的右子树,结束。
# 使用递归实现
def insert_rec(node, val): # node递归的节点 val要插入的元素
if not node: # 如果没有节点
node = BiTreeNode(val)
elif val < node.data: # 插入元素小于节点元素
node.lchild = insert_rec(node.lchild, val) # 递归左子树
node.lchild.parent = node # 连接父节点
elif val > node.data: # 插入元素大于节点元素
node.rchild = insert_rec(node.rchild, val) # 递归右子树
node.rchild.parent = node
return node
# 使用非递归实现
def insert_no_rec(val):
p = root
if not p: # 没有根节点
root = BiTreeNode(val)
return
while True: # 根节点存在,进入循环
if val < p.data:
if p.lchild: # 如果p的左孩子存在
p = p.lchild
else: # p的左孩子不存在
p.lchild = BiTreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild: # 如果p的左孩子存在
p = p.rchild
else: # p的左孩子不存在
p.rchild = BiTreeNode(val)
p.rchild.parent = p
return
else: # 如果val和某个节点的数据相等
return # 在这里也可以在节点中多定义一个用于重复元素计数的属性,遇到相等的情况,进行+1
2.1.3元素查询
# 递归实现
def query_rec(node, val): # 使用递归实现
if not node:
return None
elif val < node.data:
return query_rec(node.lchild, val)
elif val > node.data:
return query_rec(node.rchild, val)
else:
return node
# 非递归实现
def query_no_rec(val):
p = root
while True:
if val < p.data:
p = p.lchild
elif val > p.data:
p = p.rchild
else:
return p
else: # 没有找到
return None
2.1.3删除操作
1.如果要删除的节点是叶子节点:直接删除
比如要删除16这个叶子节点,直接删除
def __remove_node_1(node):
# 情况1,node是叶子节点
if not node.parent: # node没有父亲节点,说明node是根节点
root = None
if node == node.parent.lchild: # node是它父亲的左孩子
node.parent.lchild = None
else: # node是它父亲的右孩子
node.parent.rchild = None
2.如果要删除的节点只有一个孩子:将此节点的父亲与孩子连接,然后删掉该节点。
比如删除25,需要把25的右孩子与25的父亲节点连接起来,然后删除25.如果要删除根节点的话,以右边的二叉树为例,删除17,需要重新选出根节点,此时由35作为新的根节点。
def __remove_node_21(node):
# 情况2,node只有一个左孩子
if not node.parent: # node是根节点
root = node.lchild
node.lchild.parent = None
elif node == node.parent.lchild: # node是其父亲的左孩子
# node的父亲节点与node的左孩子连接
node.parent.lchild = node.lchild
node.lchild.parent = node.parent
else: # node是其父亲的右孩子
# node的父亲节点与node的左孩子连接
node.lchild.parent = node.parent
node.parent.rchild = node.lchild
def __remove_node_22(node):
# 情况2,只有一个右孩子
if not node.parent:
root = node.rchild
node.rchild.parent = None
elif node == node.parent.rchild:
# node的父亲节点与node的右孩子连接
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
else:
# node的父亲节点与node的右孩子连接
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
3.如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点。
如果要删除5这个节点,其右子树中最小的节点是7,将7替换5,并执行删除7这个节点的操作。
# 删除操作
def delete(val):
if root: # 不是空树
node = query_no_rec(val) # 利用查找函数,返回val所在的节点
if not node: # 如果没有找到val所在的节点
return False
if not node.lchild and not node.rchild: # node没有左右孩子,说明node是叶子节点
__remove_node_1(node)
elif not node.rchild: # 如果node只有左孩子 为什么可以这样判断?如果存在右孩子,会执行上一步的if
__remove_node_21(node)
elif not node.lchild: # 同理,node只有一个右孩子
__remove_node_22(node)
else: # 两个孩子都有
min_node = node.rchild # 寻找右子树中最小的节点
while min_node.lchild: # 在二叉搜索树中,左孩子比父亲节点小
min_node = min_node.lchild # 如果有左孩子,继续往下遍历,直到节点没有左孩子,此时为删除节点右子树中最小的节点
node.data = min_node.data # 用右子树中最小的数,替换删除节点的数
if min_node.rchild: # 此时min_node没有左孩子,如果存在右孩子
__remove_node_22(min_node)
else: # min_node为叶子节点
__remove_node_1(min_node)
2.2二叉搜索树的效率
平均情况下,二叉搜索树进行搜索的时间复杂度为O(lgn)
最坏情况下,二叉搜索树可能非常倾斜
解决方案:1.随机化插入
2.AVL树
3.总结
3.1 将二叉搜索树封装到类
# 节点定义
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子
self.rchild = None # 右孩子
self.parent = None # 父节点
class BST:
def __init__(self, li=None):
self.root = None # 根节点
# 插入列表元素
if li: # 列表非空
for val in li:
self.insert_no_rec(val)
# 使用递归实现
def insert_rec(self, node, val): # node递归的节点 val要插入的元素
if not node: # 如果没有节点
node = BiTreeNode(val)
elif val < node.data: # 插入元素小于节点元素
node.lchild = self.insert_rec(node.lchild, val) # 递归左子树
node.lchild.parent = node # 连接父节点
elif val > node.data: # 插入元素大于节点元素
node.rchild = self.insert_rec(node.rchild, val) # 递归右子树
node.rchild.parent = node
return node
# 使用非递归实现
def insert_no_rec(self, val):
p = self.root
if not p: # 没有根节点
self.root = BiTreeNode(val)
return
while True: # 根节点存在,进入循环
if val < p.data:
if p.lchild: # 如果p的左孩子存在
p = p.lchild
else: # p的左孩子不存在
p.lchild = BiTreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild: # 如果p的左孩子存在
p = p.rchild
else: # p的左孩子不存在
p.rchild = BiTreeNode(val)
p.rchild.parent = p
return
else: # 如果val和某个节点的数据相等
return # 在这里也可以在节点中多定义一个用于重复元素计数的属性,遇到相等的情况,进行+1
# 元素查询
# 递归实现
def query_rec(self, node, val): # 使用递归实现
if not node:
return None
elif val < node.data:
return self.query_rec(node.lchild, val)
elif val > node.data:
return self.query_rec(node.rchild, val)
else:
return node
# 非递归实现
def query_no_rec(self, val):
p = self.root
while True:
if val < p.data:
p = p.lchild
elif val > p.data:
p = p.rchild
else:
return p
else: # 没有找到
return None
def __remove_node_1(self, node):
# 情况1,node是叶子节点
if not node.parent: # node没有父亲节点,说明node是根节点
self.root = None
if node == node.parent.lchild: # node是它父亲的左孩子
node.parent.lchild = None
else: # node是它父亲的右孩子
node.parent.rchild = None
def __remove_node_21(self, node):
# 情况2,node只有一个左孩子
if not node.parent: # node是根节点
self.root = node.lchild
node.lchild.parent = None
elif node == node.parent.lchild: # node是其父亲的左孩子
# node的父亲节点与node的左孩子连接
node.parent.lchild = node.lchild
node.lchild.parent = node.parent
else: # node是其父亲的右孩子
# node的父亲节点与node的左孩子连接
node.lchild.parent = node.parent
node.parent.rchild = node.lchild
def __remove_node_22(self, node):
# 情况2,只有一个右孩子
if not node.parent:
self.root = node.rchild
node.rchild.parent = None
elif node == node.parent.rchild:
# node的父亲节点与node的右孩子连接
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
else:
# node的父亲节点与node的右孩子连接
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
# 删除操作
def delete(self, val):
if self.root: # 不是空树
node = self.query_no_rec(val) # 利用查找函数,返回val所在的节点
if not node: # 如果没有找到val所在的节点
return False
if not node.lchild and not node.rchild: # node没有左右孩子,说明node是叶子节点
self.__remove_node_1(node)
elif not node.rchild: # 如果node只有左孩子 为什么可以这样判断?如果存在右孩子,会执行上一步的if
self.__remove_node_21(node)
elif not node.lchild: # 同理,node只有一个右孩子
self.__remove_node_22(node)
else: # 两个孩子都有
min_node = node.rchild # 寻找右子树中最小的节点
while min_node.lchild: # 在二叉搜索树中,左孩子比父亲节点小
min_node = min_node.lchild # 如果有左孩子,继续往下遍历,直到节点没有左孩子,此时为删除节点右子树中最小的节点
node.data = min_node.data # 用右子树中最小的数,替换删除节点的数
if min_node.rchild: # 此时min_node没有左孩子,如果存在右孩子
self.__remove_node_22(min_node)
else: # min_node为叶子节点
self.__remove_node_1(min_node)
# 前序遍历
def pre_order(self, root):
if root: # 如果有数据
print(root.data, end=' ')
self.pre_order(root.lchild)
self.pre_order(root.rchild)
# 中序遍历
def in_order(self, root):
if root:
self.in_order(root.lchild)
print(root.data, end=' ')
self.in_order(root.rchild)
# 后序遍历
def post_order(self, root):
if root:
self.post_order(root.lchild)
self.post_order(root.rchild)
print(root.data, end=' ')
import random
li = list(range(30))
random.shuffle(li)
tree = BST(li)
tree.in_order(tree.root)
print("")
tree.delete(17)
tree.in_order(tree.root)
打印结果:
注意,在上述功能中没有处理传入元素相同的情况,所以如果传入列表中有相同的元素,最后打印时,之后打印一个。