当前位置: 首页 > news >正文

【15】数据结构之基于树的查找算法篇章

目录标题

    • 二叉排序树 Binary Sort Tree
      • 二叉排序树的插入
      • 二叉树排序树的删除
      • 二叉排序树的查找
      • 二叉排序树的调试与代码集合
    • 平衡二叉树-AV树
      • 平衡二叉树的平衡化旋转
      • 平衡二叉树的代码调试与代码集合
    • B树
      • B树的查找
      • B树的插入
      • B+树和B*树

二叉排序树 Binary Sort Tree

  • 二叉查找树,也称二叉搜索树
  • 性质
    • 或是一棵空树
    • 如果左子树不为空,则左子树上所有结点的值均小于它根结点的值
    • 如果右子树不为空,则右子树上所有结点的值均大于它根结点的值
    • 左右子树也都为二叉排序树
    • 树中没有值相同的结点
  • 二叉排序树的示意图
    在这里插入图片描述

二叉排序树的插入

  • 基本思想:已知一关键字为key的结点node

      1. 若二叉树为空树,则结点node成为二叉排序树的根
      1. 若二叉树非空树,则将node.key与二叉排序树根结点的关键字进行比较
      • 如果key的值等于根结点的值,则停止插入
      • 如果key的值小于根结点的值,则将结点node插入左子树
      • 如果key的值大于根结点的值,则将结点node插入右子树
  • 二叉排序树的插入操作
    在这里插入图片描述

  • 二叉排序树的插入代码

    def insert(self, key):"""插入新结点:param key: 待插入元素:return:"""node = Node(key)# 如果二叉排序树为空树if self.root == None:self.root = node# 如果二叉树不为空树else:# 创建一个指针指向根结点cur = self.root# cur不为空while cur != None:# 判断key与cur.val的大小if key > cur.val:# 在右子树中插入新结点if cur.right == None:cur.right = nodebreakcur = cur.rightelse:# 在左子树插入新结点if cur.left == None:cur.left = nodebreakcur = cur.left

二叉树排序树的删除

核心操作:

  • 删除结点不在二叉排序树中,则不做任何操作
  • 否则,假设需要删除的结点为key,key的双亲结点为keyParent,假设key为keyParent的左孩子结点
    • 1.若key为叶子结点,则直接删除
    • 2.若key只有左子树,可将key的左子树直接改为其双亲结点keyParent的左子树
    • 若key有左右子树
      • 找到key在中序序列中的前驱结点pre
      • 将key的左子树改为结点keyParent的左子树
      • 将key的右子树改为中序序列中的前驱结点pre的右子树
  • 二叉排序树的删除操作
    在这里插入图片描述
  • 二叉排序树的删除代码
    def delete(self, key):"""二叉排序树的删除:param key: 删除的元素值:return:"""if self.root is None:return# target指向根结点target = self.root# targetParent指向target的双亲结点targetParent = None# 从树根开始逐层向下查找待删除的数值为key的结点while target and target.val != key:targetParent = target# 找到对应结点则退出循环# 在左子树中继续查找if key < target.val:target = target.leftelse:# 在右子树中继续查找target = target.rightif target is None:  # 未找到节点return# 情况1:目标节点是叶子节点if target.left is None and target.right is None:if targetParent is None:  # 根节点self.root = Noneelif targetParent.left == target:targetParent.left = Noneelse:targetParent.right = None# 情况2:目标节点是单分支节点elif target.left is None or target.right is None:child = target.left if target.left else target.rightif targetParent is None:  # 根节点self.root = childelif targetParent.left == target:targetParent.left = childelse:targetParent.right = child# 情况3:目标节点是双分支节点else:# 找到左子树的最大节点(中序前驱)predecessor_parent = targetpredecessor = target.leftwhile predecessor.right:predecessor_parent = predecessorpredecessor = predecessor.right# 用前驱节点的值替换目标节点值target.val = predecessor.val# 删除前驱节点(前驱节点无右子树)if predecessor_parent.left == predecessor:predecessor_parent.left = predecessor.leftelse:predecessor_parent.right = predecessor.left

二叉排序树的查找

  • 基本思想:将待查关键字key与根结点关键字val进行比较

    • key == val,则返回根结点地址
    • key < val,则进一步查找左子树
    • key > val,则进一步查找右子树
  • 二叉排序树的平均查找长度ASL在最好的情况下为 O ( l o g 2 n ) O(log_2 n) O(log2n)

  • 二叉排序树的查找操作
    在这里插入图片描述

  • 二叉排序树的代码

    def contains(self, key):"""查找:param key::return:"""if self.root == None:return Falsecur = self.rootwhile cur != None:#如果key等于cur指针指向的结点数值if key == cur.val:return True# 如果大于其数值elif key > cur.val:# 将cur指针指向的结点的右孩子结点cur = cur.rightelse:# 否则指向左孩子结点cur = cur.left# 查找失败的情况return False

二叉排序树的调试与代码集合

class Node(object):def __init__(self, val):self.val = valself.left = Noneself.right = Noneclass BinarySortTree(object):def __init__(self):self.root = Nonedef insert(self, key):"""插入新结点:param key: 待插入元素:return:"""node = Node(key)# 如果二叉排序树为空树if self.root == None:self.root = node# 如果二叉树不为空树else:# 创建一个指针指向根结点cur = self.root# cur不为空while cur != None:# 判断key与cur.val的大小if key > cur.val:# 在右子树中插入新结点if cur.right == None:cur.right = nodebreakcur = cur.rightelse:# 在左子树插入新结点if cur.left == None:cur.left = nodebreakcur = cur.leftdef getMinNode(self, node):"""获取二叉排序树中的数值最小结点:param node: 根结点:return:"""# 如果根结点的左子树为空,因此根结点数值最小if node.left == None:return node.val# 如果根结点的左子树不为空,数值最小的结点一定在根结点的左子树# 用指针cur指向根结点的左孩子结点cur = node.leftwhile cur.left != None:cur = cur.left# 如果当前结点的左子树为空,则当前结点数值为最小值return cur.valdef getMaxNode(self, node):"""获取二叉排序树中数值最大的结点:param node: 根结点:return:"""# 如果根结点的右子树为空树,则根结点数值最大if node.right == None:return node.val# 如果根结点的右子树不为空树,数值最大的结点一定在根结点的右子树# 用指针cur指向根结点的右孩子结点cur = node.rightwhile cur.right != None:cur = cur.right# 如果当前结点的右子树为空树,则当前结点数值为最大值return cur.valdef preorder_iterator(self,node):"""先序遍历:param node: 二叉顺序树的根结点:return:"""if node != None:print(node.val, end=" ")self.preorder_iterator(node.left)self.preorder_iterator(node.right)def inorder_iterator(self, node):"""中序遍历:param node: 二叉树的根结点:return:"""if node != None:self.inorder_iterator(node.left)print(node.val, end=" ")self.inorder_iterator(node.right)def postorder_iterator(self, node):"""后序遍历:param node: 二叉树的根结点:return:"""if node != None:self.postorder_iterator(node.left)self.postorder_iterator(node.right)print(node.val, end=" ")def delete(self, key):"""二叉排序树的删除:param key: 删除的元素值:return:"""if self.root is None:return# target指向根结点target = self.root# targetParent指向target的双亲结点targetParent = None# 从树根开始逐层向下查找待删除的数值为key的结点while target and target.val != key:targetParent = target# 找到对应结点则退出循环# 在左子树中继续查找if key < target.val:target = target.leftelse:# 在右子树中继续查找target = target.rightif target is None:  # 未找到节点return# 情况1:目标节点是叶子节点if target.left is None and target.right is None:if targetParent is None:  # 根节点self.root = Noneelif targetParent.left == target:targetParent.left = Noneelse:targetParent.right = None# 情况2:目标节点是单分支节点elif target.left is None or target.right is None:child = target.left if target.left else target.rightif targetParent is None:  # 根节点self.root = childelif targetParent.left == target:targetParent.left = childelse:targetParent.right = child# 情况3:目标节点是双分支节点else:# 找到左子树的最大节点(中序前驱)predecessor_parent = targetpredecessor = target.leftwhile predecessor.right:predecessor_parent = predecessorpredecessor = predecessor.right# 用前驱节点的值替换目标节点值target.val = predecessor.val# 删除前驱节点(前驱节点无右子树)if predecessor_parent.left == predecessor:predecessor_parent.left = predecessor.leftelse:predecessor_parent.right = predecessor.leftdef contains(self, key):"""查找:param key::return:"""if self.root == None:return Falsecur = self.rootwhile cur != None:#如果key等于cur指针指向的结点数值if key == cur.val:return True# 如果大于其数值elif key > cur.val:# 将cur指针指向的结点的右孩子结点cur = cur.rightelse:# 否则指向左孩子结点cur = cur.left# 查找失败的情况return Falseif __name__ == "__main__":print('PyCharm')bstree = BinarySortTree()temp = [53, 78, 65, 17, 87, 9, 81, 15]# 插入结点for i in temp:bstree.insert(i)# 删除操作bstree.delete(17)# 查找操作num = 77print(f"查找的元素:{num}在二叉树中的查找情况为{bstree.contains(num)}")print("先序遍历:", end="")bstree.preorder_iterator(bstree.root)print()print("中序遍历:", end="")bstree.inorder_iterator(bstree.root)print()print("后序遍历:", end="")bstree.postorder_iterator(bstree.root)print()min_num = bstree.getMinNode(bstree.root)print('二叉排序树的最小结点值为:', min_num)max_num = bstree.getMaxNode(bstree.root)print("二叉排序树的最大结点值为:", max_num)

平衡二叉树-AV树

  • 满足任何一个结点的左子树和右子树的高度差的绝对值不超过1的二叉排序树.
  • 性质
    • 或是一棵空树
    • 左子树与右子树的高度差的绝对值小于或等于1;
    • 左子树和右子树也都是平衡树
    • 1.有n个结点的平衡二叉树的高度为 l o g 2 n log_2 n log2n
    • 2.在有n个结点的平衡二叉树中搜索一个结点的时间复杂度为 O ( l o g 2 n ) O(log_2 n) O(log2n)
    • 3.将一个新结点插入一棵有n个结点的平衡二叉树中,可得到一棵有n+1个结点的平衡二叉树,且插入的时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n)
    • 4.从一棵有n个结点的平衡二叉树删除一个结点,可得到一棵有n-1个结点的平衡二叉树,且删除的时间复杂度为 O ( l o g 2 n ) O(log_2 n) O(log2n)
    • 5.结点的平衡因子BF:定义为结点左子树与右子树高度之差;在平衡二叉树中任一结点的BF值只能是-1,0,和1.

平衡二叉树的平衡化旋转

在这里插入图片描述

  • 平衡二叉树的创建

在这里插入图片描述

  • 若在平衡二叉树中插入一个新结点,则会造成不平衡,需要进行平衡化旋转.
  • 平衡化旋转
    • 单旋转(LL旋转和RR旋转)
    • 双旋转(LR旋转和RL旋转)
      • LL:用于处理左子树的左子树过高
      • RR:用于处理左子树的左子树过高
      • LR:用于处理左子树的右子树过高
      • RL:用于处理右子树的左子树过高

平衡二叉树的代码调试与代码集合

class Node(object):def __init__(self, data):self.data = dataself.left = Noneself.right = Noneself.height = 1  # 初始高度为1(叶子节点)class AVLTree(object):def __init__(self):self.root = Nonedef height(self, node):if node is None:return 0return node.heightdef update_height(self, node):"""更新节点高度"""node.height = max(self.height(node.left), self.height(node.right)) + 1def balance_factor(self, node):"""计算平衡因子"""if node is None:return 0return self.height(node.left) - self.height(node.right)def is_balance(self, node):"""验证AVL树是否平衡"""if node is None:return Trueleft_height = self.height(node.left)right_height = self.height(node.right)if abs(left_height - right_height) > 1:return Falsereturn self.is_balance(node.left) and self.is_balance(node.right)# ---------------------- 遍历方法 ----------------------def preorder_iterator(self, node):if node is not None:print(node.data, end=" ")self.preorder_iterator(node.left)self.preorder_iterator(node.right)def inorder_iterator(self, node):if node is not None:self.inorder_iterator(node.left)print(node.data, end=" ")self.inorder_iterator(node.right)def postorder_iterator(self, node):if node is not None:self.postorder_iterator(node.left)self.postorder_iterator(node.right)print(node.data, end=" ")def LL(self, node):"""左左旋转(右旋)"""new_root = node.leftnode.left = new_root.rightnew_root.right = nodeself.update_height(node)self.update_height(new_root)return new_rootdef RR(self, node):"""右右旋转(左旋)"""new_root = node.rightnode.right = new_root.leftnew_root.left = nodeself.update_height(node)self.update_height(new_root)return new_rootdef LR(self, node):"""左右旋转(先左旋后右旋)"""node.left = self.RR(node.left)return self.LL(node)def RL(self, node):"""右左旋转(先右旋后左旋)"""node.right = self.LL(node.right)return self.RR(node)def find_min(self, node):"""查找最小节点"""if node is None or node.left is None:return nodereturn self.find_min(node.left)def insert(self, data, node):"""插入节点并平衡"""if node is None:return Node(data)if data < node.data:node.left = self.insert(data, node.left)elif data > node.data:node.right = self.insert(data, node.right)else:return node  # 重复值不插入# 更新高度self.update_height(node)# 检查平衡因子balance = self.balance_factor(node)# 左子树不平衡if balance > 1:if self.balance_factor(node.left) >= 0:  # 左左型return self.LL(node)else:  # 左右型return self.LR(node)# 右子树不平衡if balance < -1:if self.balance_factor(node.right) <= 0:  # 右右型return self.RR(node)else:  # 右左型return self.RL(node)return nodedef remove(self, data, node):"""删除节点并平衡"""if node is None:return Noneif data < node.data:node.left = self.remove(data, node.left)elif data > node.data:node.right = self.remove(data, node.right)else:# 找到目标节点if node.left is None or node.right is None:# 单分支或叶子节点child = node.left if node.left else node.rightif child is None:node = Noneelse:node = childelse:# 双分支节点:用右子树最小值替换并删除min_node = self.find_min(node.right)node.data = min_node.datanode.right = self.remove(min_node.data, node.right)if node is None:return None# 更新高度self.update_height(node)# 检查平衡因子balance = self.balance_factor(node)# 左子树不平衡if balance > 1:if self.balance_factor(node.left) >= 0:  # 左左型return self.LL(node)else:  # 左右型return self.LR(node)# 右子树不平衡if balance < -1:if self.balance_factor(node.right) <= 0:  # 右右型return self.RR(node)else:  # 右左型return self.RL(node)return node# ---------------------- 测试用例 ----------------------
if __name__ == "__main__":avltree = AVLTree()data = [16, 3, 7, 11, 9, 26, 18, 14, 15]# 插入节点for num in data:avltree.root = avltree.insert(num, avltree.root)assert avltree.is_balance(avltree.root), f"插入{num}后树不平衡!"print("初始树结构:")print("前序遍历:", end="")avltree.preorder_iterator(avltree.root)print("\n中序遍历:", end="")avltree.inorder_iterator(avltree.root)print("\n后序遍历:", end="")avltree.postorder_iterator(avltree.root)print("\n")# 删除节点avltree.root = avltree.remove(7, avltree.root)  # 删除中间节点avltree.root = avltree.remove(16, avltree.root)  # 删除根节点print("删除后树结构:")print("前序遍历:", end="")avltree.preorder_iterator(avltree.root)print("\n中序遍历:", end="")avltree.inorder_iterator(avltree.root)print("\n后序遍历:", end="")avltree.postorder_iterator(avltree.root)

B树

在这里插入图片描述

  • 多路平衡查找树
  • 需要满足一下要求,定义出一棵m阶的B树
    • 或一棵空树
    • 1.树中每个结点最多有m棵子树(m>=2)
    • 2.根结点至少有两个子结点
    • 3.除根结点外,结点中关键字的个数取值范围为(m/2)-1到m-1(m/2向上取整)
    • 4.所有叶子结点都在同一层
    • 5.除根结点和叶子结点外,如果结点有k-1个关键字,这个结点就有k个子结点,关键字按递增顺序排序

B树的查找

  • 把根结点读出,在根结点所包含的关键字中进行查找给定元素关键字,找到则查找成功
  • 否则,确定要查找关键字大小的范围,根据指针指向子结点,在子结点中继续查找,如果查找到叶子结点仍未找到,表示查找失败.
  • 查找示意图
    在这里插入图片描述

B树的插入

  • 先确定插入元素在B树是否已经存在,若不存在则进行插入操作
  • 若找到插入的位置结点空间足够,则顺利完成插入;否则无法插入.
    在这里插入图片描述

B+树和B*树

  • B+树和B数的差异
    • 1.有k个子结点的B+树中含有k个关键字,非叶子结点中的每个关键字不保存数据,只用来索引,所有数据都保存在叶子结点中.
    • 2.所有的叶子结点包含全部关键字信息,以及指向含有这些关键字的指针,且叶子结点本身是依关键字值递增的链表
    • 3.所有的非终端结点可以视为索引,结点中仅含其子树根结点中最大或最小的关键字.
  • B*与B+树
    • B是B+树的一种变形,在B+树的基础上,B树为非根结点和非叶子结点增加了指向兄弟结点的指针
    • B*树定义了非叶子结点的关键字数量至少为(2/3)×m,即块的最低使用率为2/3(B+树的使用率为1/2).

相关文章:

  • 信奥赛之c++基础(取模运算与数位分离)
  • aslist和list的区别
  • 【Linux】进程池bug、命名管道、systemV共享内存
  • 【实战篇】数字化打印——打印管理模块的业务设计(一)
  • ES通过API操作索引库
  • 分布式计算领域的前沿工具:Ray、Kubeflow与Spark的对比与协同
  • 蓝桥杯常用的APi
  • spatk-sql核心
  • ngx_conf_handler - worker_connections 1024
  • OpenResty与Nginx的功能对比分析
  • visual studio 如何在 release 模式下调试
  • 中美电力标准差异下电机运行的影响及应对策略
  • 接口和抽象的区别?日常使用场景
  • WINUI——Background颜色使用小结
  • 2 cline 提示词工程指南-记忆库
  • 基于PySide6与pycatia的CATIA智能倒角工具开发全解析
  • 4月15号
  • 欧冠002:阿斯顿维拉 vs 巴黎圣日耳曼,维拉强攻致防线大开
  • SP B\nRebuild Priorit> 如何用python去掉\n
  • 用python比较两个mp4是否实质相同
  • 马上评丨别让“免费领养”套路坑消费者又坑宠物
  • 市场监管总局:2024年查办商标、专利等领域违法案件4.4万件
  • AI观察|算力饥渴与泡沫
  • 财政部农业农村司司长吴奇修接受纪律审查和监察调查
  • CSR周刊:李宁打造世界地球日特别活动,珀莱雅发布2024年度可持续发展报告
  • 伊朗港口爆炸已造成25人死亡,灭火行动已近尾声