b站视频:路飞IT学城
清华计算机博士带你学习Python算法+数据结构_哔哩哔哩_bilibili

#71 二叉搜索树:查询

import randomclass BiTreeNode:def __init__(self, data):self.data = dataself.lchild = None  # 左孩子self.rchild = None  # 右孩子self.parent = Noneclass BST:def __init__(self, li=None):self.root = Noneif li:for val in li:self.insert_no_rec(val)# 递归写法def insert(self, node, val):if not node:node = BiTreeNode(val)elif val < node.data:node.lchild = self.insert(node.lchild, val)node.lchild.parent = nodeelif val > node.data:node.rchild = self.insert(node.rchild, val)node.rchild.parent = nodereturn node# 不递归的写法def insert_no_rec(self, val):p = self.rootif not p:               # 空树self.root = BiTreeNode(val)returnwhile True:if val < p.data:if p.lchild:p = p.lchildelse:           # 左孩子不存在p.lchild = BiTreeNode(val)p.lchild.parent = preturnelif val > p.data:if p.rchild:p = p.rchildelse:p.rchild = BiTreeNode(val)p.rchild.parent = preturnelse:return#注:查询:有递归版本 和 非递归版本def query(self, node, val):   #注:递归版本  都要带一个node#注:因为node是用来递归的if not node:    #注:如果 node是空 或者是 往里找找到最底层 ,就是找不到 ,递归的终止条件return None #注:就返回None#注:剩下的话 分3种情况if node.data < val: #注:如果val大于它 ,就往右边找return self.query(node.rchild, val) #注:return 返回查询的结果 nodeelif node.data > val:   #注:如果val小于它 ,就往左边找return self.query(node.lchild, val)else:       #注:第3 种情况,等于,就直接返回nodereturn node#注:非递归 的查询def query_no_rec(self, val):p = self.root   #注:从p开始找while p:    #注:只要p不是空   3种情况if p.data < val:    #注:去右边找p = p.rchildelif p.data > val:  #注:往左边找p = p.lchildelse:   #注:情况3:等于  直接返回preturn p#注:如果说 循环执行完了,从这出来了 ,p是空的,说明找不到return None #注:就返回Noneli = list(range(0, 500, 2)) #注:生成的都是偶数
random.shuffle(li)tree = BST(li)
#注:非递归的  查询
print(tree.query_no_rec(4)) #注:返回的是 node对象
#结果为 <__main__.BiTreeNode object at 0x0000014998433340>
print(tree.query_no_rec(4).data)
#结果为 4
#注:也没有别的data。也可以说 data  这里面有1个key,1个value,判断相等不相等按照key来排,获取的是value,就跟字典差不多
print(tree.query_no_rec(3)) #注:3不存在  ,因为 里面全是偶数
#结果为 None

精简代码

class BiTreeNode:def __init__(self, data):self.data = dataself.lchild = None   # 左孩子self.rchild = None  # 右孩子self.parent = Noneclass BST:def __init__(self, li=None):self.root = Noneif li:for val in li:self.insert_no_rec(val)def insert(self, node, val):if not node:node = BiTreeNode(val)elif val < node.data:node.lchild = self.insert(node.lchild, val)node.lchild.parent = nodeelif val > node.data:node.rchild = self.insert(node.rchild, val)node.rchild.parent = nodereturn nodedef insert_no_rec(self, val):p = self.rootif not p:               # 空树self.root = BiTreeNode(val)returnwhile True:if val < p.data:if p.lchild:p = p.lchildelse:           # 左孩子不存在p.lchild = BiTreeNode(val)p.lchild.parent = preturnelif val > p.data:if p.rchild:p = p.rchildelse:p.rchild = BiTreeNode(val)p.rchild.parent = preturnelse:returndef query(self, node, val):  #注:递归查询if not node:return Noneif node.data < val:return self.query(node.rchild, val)elif node.data > val:return self.query(node.lchild, val)else:return nodedef query_no_rec(self, val):   #注:非递归查询p = self.rootwhile p:if p.data < val:p = p.rchildelif p.data > val:p = p.lchildelse:return preturn Noneli = list(range(0, 500, 2))
random.shuffle(li)tree = BST(li)
print(tree.query_no_rec(3))
#结果 None

#72 二叉搜索树:删除

二叉搜索树 – 删除操作
1.如果要删除的节点是叶子节点:直接删除

2.如果要删除的节点只有一个孩子:将此节点的父亲与孩子连接,然后删除该节点。

3、如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点。

#注:删除比较复杂 ,分3种情况
#注:情况1:如果要删的节点是 叶子节点;直接删除 (因为它没有孩子)#注:情况2:如果要删除的节点只有一个孩子;将此节点的父亲与孩子连接,然后删除该节点。(比如删25;把孩子17跟父亲35连起来,再删除25节点。左孩子也一样)
#注:不管左孩子、右孩子,都连在17的右边,因为它们肯定比17大。
#注:特殊情况:如果要删除的节点 是根,怎么办?比如说只有 17 右孩子35 (左孩子 29 右孩子 38),把35 跟 17的父亲(None)连起来,但是要更新根#注:情况3:如果删除的节点  有2个孩子,那么将它右子树的最小节点删除,并且替换当前节点
#注:比如说删5,删掉后这个位置就没了,谁来当这个根?找比它小一点点或者大一点点的那个数
#注:找右子树里最小的数  7 (怎么找右子树里最小的数?从它的右子树开始(17开始),一直往左边,走到头,不能往右边找,就是最小,把它拿过来放到那个位置,并且把这个位置删了)
#注:要找的最小节点 ,最多只有一个孩子,肯定没有左孩子。如果这个节点是一个叶子,就按照情况1来;如果这个节点只有1个右孩子,就按照情况2来

#73 二叉搜索树:删除实现

#注:先写前2种情况,再写第3种情况,因为情况3 会用前2种情况

import randomclass BiTreeNode:def __init__(self, data):self.data = dataself.lchild = None   # 左孩子self.rchild = None  # 右孩子self.parent = Noneclass BST:def __init__(self, li=None):self.root = Noneif li:for val in li:self.insert_no_rec(val)def insert(self, node, val):if not node:node = BiTreeNode(val)elif val < node.data:node.lchild = self.insert(node.lchild, val)node.lchild.parent = nodeelif val > node.data:node.rchild = self.insert(node.rchild, val)node.rchild.parent = nodereturn nodedef insert_no_rec(self, val):p = self.rootif not p:               # 空树self.root = BiTreeNode(val)returnwhile True:if val < p.data:if p.lchild:p = p.lchildelse:           # 左孩子不存在p.lchild = BiTreeNode(val)p.lchild.parent = preturnelif val > p.data:if p.rchild:p = p.rchildelse:p.rchild = BiTreeNode(val)p.rchild.parent = preturnelse:returndef query(self, node, val):if not node:return Noneif node.data < val:return self.query(node.rchild, val)elif node.data > val:return self.query(node.lchild, val)else:return nodedef query_no_rec(self, val):p = self.rootwhile p:if p.data < val:p = p.rchildelif p.data > val:p = p.lchildelse:return preturn Nonedef 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=',')#-----------------删除的方法-------------------#注:情况1.删除的节点 是一个叶子节点def __remove_node_1(self, node):    #注:node是叶子节点# 情况1:node是叶子节点if not node.parent:     #注:如果node是根,说明树里只有1个节点 根self.root = None    #注:直接把它变成空树#注:不是根的情况,把该节点父亲的孩子 指成None就可以了#注:判断 它是它父亲的 左孩子 还是 右孩子if node == node.parent.lchild:  #注:如果 node 是它父亲的左孩子node.parent.lchild = None   #注:父亲和它断联系,这样 从根就联系不到它# node.parent = None        #注:这句写不写无所谓,从根找不到这个节点了else:                           #注:如果 node 是它父亲的右孩子node.parent.rchild = None   #注:父亲和它断联系#注:情况2.删除的节点 只有一个孩子 (代码需要区分是左孩子 还是 右孩子)#注:情况2.1:node只有一个左孩子def __remove_node_21(self, node):# 情况2.1:node只有一个左孩子if not node.parent:             #注:先判断 它是不是 根节点self.root = node.lchild      #注:如果是根节点,它的左孩子 就成为新的根了node.lchild.parent = None    #注:新的根的父亲 要置空,没有elif node == node.parent.lchild:  #注:如果该节点 是它父亲的左孩子node.parent.lchild = node.lchild    #注:它父亲 和它孩子(左孩子)要连接起来,即它父亲的左孩子等于它的孩子node.lchild.parent = node.parent    #注:反着连回去,它的左孩子的父亲 等于 它的父亲

    else:                              #注:如果该节点 是它父亲的右孩子node.parent.rchild = node.lchild    #注:那么它父亲就得跟它的孩子(只有1个左孩子) 通过右孩子的方式连接node.lchild.parent = node.parent    #注:往回指,它的左孩子的父亲,指向 它的父亲#注:情况2.2:node只有一个右孩子,跟之前差不多
def __remove_node_22(self, node):# 情况2.2:node只有一个右孩子if not node.parent:    #注:先判断 它是不是根节点self.root = node.rchild #注:因为它只有一个 右孩子#注:分情况 1、它是它父亲的左孩子;2、它是它父亲的右孩子elif node == node.parent.lchild:    #注:如果它是它父亲的左孩子 (它只有一个右孩子)node.parent.lchild = node.rchild    #注:它的孩子(右孩子) 和它的父亲 通过 左孩子连起来 (因为它是它父亲的左孩子)node.rchild.parent = node.parent    #注:反着连回去

    else:                               #注:如果它是它父亲的右孩子 (它只有一个右孩子)node.parent.rchild = node.rchild    #注:它的孩子(右孩子) 和它的父亲 通过 右孩子连起来 (因为它是它父亲的右孩子)node.rchild.parent = node.parent    #注:反着连回去#注:情况3:用到情况1和情况2,所以不单独写。直接写总的delete#注:这是所有情况的删除def delete(self, val):#注:先判断 是不是空树,是空树的话 什么都不做if self.root:    #注:不是空树的话,先找到那个节点node = self.query_no_rec(val)#注:先找到那个节点if not node: #注:如果node不存在,树里不存在nodereturn False    #注:或者raise error#注:如果node存在,3种情况#注:情况1:node是叶子节点(即 没有左孩子 也没有右孩子)if not node.lchild and not node.rchild: #注:情况1. 叶子节点self.__remove_node_1(node)  #注:加两下滑线,说明是私有的,在类里用就行了#注:情况2   2.1 node只有一个左孩子 (即 它右孩子没有的时候)elif not node.rchild:       # 2.1 只有一个左孩子。因为它一个孩子都没有的话  不会执行到这里self.__remove_node_21(node)#注:情况2   2.2 node只有一个右孩子 (即 它左孩子没有的时候)elif not node.lchild:       # 2.2 只有一个右孩子。因为它一个孩子都没有的话  不会执行到这里self.__remove_node_22(node)#注:情况3   node两个孩子都有else:   # 3. 两个孩子都有#注:先找 右子树里 最小的节点min_node = node.rchild  #注:它肯定有右孩子while min_node.lchild:  #注:如果node.rchild 有左孩子,一直循环min_node = min_node.lchild  #注:那么min_node=min_node.lchild#注:这个while 执行完了以后,min_node就是它右子树里最小的那个节点#注:然后 把min_node(即最小的那个节点) 替换到node这来node.data = min_node.data#注:然后 删除min_node : 把min_node 原来的位置删了#注:删除min_node又需要判断 min_node满足情况1还是情况2。min_node肯定不满足情况3,因为 min_node肯定没有左孩子if min_node.rchild: #注:如果min_node有右孩子 ,满足 情况2.2  只有一个右孩子self.__remove_node_22(min_node)else:               #注:如果min_node没有右孩子,那它一定是叶子节点,满足情况1self.__remove_node_1(min_node)
#-----------------删除的方法-------------------
#测试
tree = BST([1,4,2,5,3,8,6,9,7])
tree.in_order(tree.root)    #注:中序遍历
print("")
tree.delete(4)
tree.delete(1)
tree.delete(8)
tree.in_order(tree.root)
#结果为        #注:4、1、8 少了
# 1,2,3,4,5,6,7,8,9,
# 2,3,5,6,7,9,

精简代码 二叉搜索树 -- 删除操作

    def __remove_node_1(self, node):# 情况1:node是叶子节点if not node.parent:self.root = Noneif node == node.parent.lchild:  #node是它父亲的左孩子node.parent.lchild = Noneelse:   #右孩子node.parent.rchild = Nonedef __remove_node_21(self, node):# 情况2.1:node只有一个左孩子if not node.parent: # 根节点self.root = node.lchildnode.lchild.parent = Noneelif node == node.parent.lchild:node.parent.lchild = node.lchildnode.lchild.parent = node.parentelse:node.parent.rchild = node.lchildnode.lchild.parent = node.parentdef __remove_node_22(self, node):# 情况2.2:node只有一个右孩子if not node.parent:self.root = node.rchildelif node == node.parent.lchild:node.parent.lchild = node.rchildnode.rchild.parent = node.parentelse:node.parent.rchild = node.rchildnode.rchild.parent = node.parentdef delete(self, val):if self.root:   # 不是空树node = self.query_no_rec(val)if not node: # 不存在return Falseif not node.lchild and not node.rchild: #1. 叶子节点self.__remove_node_1(node)elif not node.rchild:       # 2.1 只有一个左孩子self.__remove_node_21(node)elif not node.lchild:       # 2.2 只有一个右孩子self.__remove_node_22(node)else:   # 3. 两个孩子都有min_node = node.rchildwhile min_node.lchild:min_node = min_node.lchildnode.data = min_node.data# 删除min_nodeif min_node.rchild:self.__remove_node_22(min_node)else:self.__remove_node_1(min_node)

#74 AVL树的概念

###### 二叉搜索树的效率
# 平均情况下,二叉搜索树进行搜索的时间复杂度为O(lgn)。
# 最坏情况下,二叉搜索树可能非常偏斜。
# 解决方案:
#   ·随机化插入
#   ·AVL树

#注:二叉搜索树 做搜索或者插入 时间复杂度 平均情况都是logn
#注:但是 最坏情况 非常偏斜,时间复杂度 会退化成O(n),如图,假如 输入序列是 10 20 30 40 50 ,插入时  如图,就和链表差不多了,查找的时候 还得一个一个查
#注:所以它 就会非常偏斜,导致 从一个树型结构  ,退化成线型结构。
#注:快速排序 也有这类问题,9 8 7 6 5 4 3 2 1 。快排 提供了一个 随机化找,不是找第一个元素,随便找一个元素。这里也可以,插入的序列打乱一下。但是有问题,打乱一下 有时候 是过段时间来插入一次
#注:为了解决这个 问题,从二叉搜索树的基础上,提出了一种新的改进的 数据结构,叫做 AVL树
###### AVL树
# AVL树:AVL树是一棵自平衡的二叉搜索树。
# AVL树具有以下性质:
#   ·根的左右子树的高度之差的绝对值不能超过1
#   ·根的左右子树都是平衡二叉树  #注:意思是 任何节点 左右子树高度差 不能超过1

#注:AVL 发明这个树的三位科学家的 首字母缩写
#注:AVL树是一棵自平衡的二叉搜索树。什么叫平衡?如图所示 就叫平衡:任何一个节点 2个子树的高度差 不能超过1 (也就是说 可以是 0 +-1,但不能是2)

#注:50平衡 左子树/右子树 高度0;40平衡 0 和 1;30不平衡 0 和 2,差为2
#注:balance factor 平衡因子 记录左右子树 高度差  (左 减 右)
#注:balance factor 只有0和正负1  ,那么是 AVL树
#注:如何维护(插入)?

#75 AVL旋转

#注:插入1

#注:从它(1) 往回找,找这个插入 是不是 引发了一个节点的不平衡

#注:往回走 ,找到6,不平衡了,左3 右1

#注:平衡了 这个树转了一下,叫做旋转

###### AVL树 -- 插入
# 插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。
# 插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树的高度差2。(因为只插入1个,所有只会是2)
# 不平衡的出现可能有4种情况### AVL插入——左旋
# 1.不平衡是由于对K的右孩子的右子树插入导致的:左旋

做一次左旋,简单情况

#注:解决方法 左旋,把P往左转,C的S2变成P的孩子

### AVL插入——右旋
# 2.不平衡是由于对K的左孩子的左子树插入导致的:右旋

右旋

右旋 ,把P往右转,C变成根,C原来的右子树 变成P的左子树 (S2)

### AVL插入——右旋-左旋
# 3.不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋
  • 1
  • 2

先右旋(20拽下来,15变成根) 再左旋(15变成根,10拽下来),简单情况

真实情况
先右旋(把C拽下来,P下面,以G为根,G的右边是C,G的左边是S2。S3变成C的左孩子,S4是C的右孩子)
再右旋(把P拽下来,G为根,G的左孩子是P,S2变为P的右孩子)


### AVL插入——左旋-右旋
# 4.不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋
#注:先左旋 25作为根,20拽下来
#注:再右旋 25作为根,30拽下来

#注:4种情况
# 右右 就左旋
# 左左 就右旋
# 左右 就左右
# 右左 就右左

从插入的节点 到根节点 往回找,找到第一个节点 balance factor 是2或-2 , 根据情况进行选择,结束后 平衡

#76 AVL:旋转实现1

from bst import BiTreeNode, BST# 继承,复用代码
class AVLNode(BiTreeNode):    # 节点类def __init__(self, data):BiTreeNode.__init__(self, data)self.bf = 0            # 数据域 存balance factor(插入时会用)class AVLTree(BST):def __init__(self, li=None):BST.__init__(self, li) # 调用BST的构造函数# 4个旋转
#---------左旋、右旋-----------------------------------------def rotate_left(self, p, c):    # 左旋 p、c都是节点,右右s2 = c.lchild               # s1、s3都没有变,s2变化p.rchild = s2               # s2给pif s2:                      # 判断s2是否空s2.parent = pc.lchild = pp.parent = c# 更新balance factor 定义右边重 就是负p.bf = 0                    # 如果有删除操作,代码错误c.bf = 0def rorate_right(self, p, c):   # 右旋,左左情况s2 = c.rchild               # 只有 s2变p.lchild = s2if s2:s2.parent = pc.rchild = pp.parent = cp.bf = 0                    # 插入操作正确,删除时 代码错误c.bf = 0#---------左旋、右旋-----------------------------------------def insert_no_rec(self, val):   # 重写,覆盖父类pass#注:左旋、右旋 差别 :l换成r,r换成l。对称
#注:如果 函数里有 +1 、-1 ,也要翻过去


#77 AVL:旋转实现2

from bst import BiTreeNode, BST# 继承,复用代码
class AVLNode(BiTreeNode):    # 节点类def __init__(self, data):BiTreeNode.__init__(self, data)self.bf = 0            # 数据域 存balance factor(插入时会用)class AVLTree(BST):def __init__(self, li=None):BST.__init__(self, li) # 调用BST的构造函数# 4个旋转
#---------左旋、右旋-----------------------------------------def rotate_left(self, p, c):    # 左旋 p、c都是节点,右右s2 = c.lchild               # s1、s3都没有变p.rchild = s2               # s2给pif s2:                      # 判断s2是否空s2.parent = pc.lchild = pp.parent = c# 更新balance factor 定义右边重 就是负p.bf = 0                    # 如果有删除操作,代码错误c.bf = 0def rorate_right(self, p, c):   # 右旋,左左情况s2 = c.rchild               # 只有 s2变p.lchild = s2if s2:s2.parent = pc.rchild = pp.parent = cp.bf = 0                    # 插入操作正确,删除时代码错误c.bf = 0#---------左旋、右旋-----------------------------------------# ---------右旋-左旋、左旋-右旋-----------------------------------------def rotate_right_left(self, p, c):  # 右旋-左旋g = c.lchild                     # 多了g# s2、s3本来属于G,全程s2、s3变化# 旋转结束 s2属于P s3属于C G为小根s3 = g.rchildc.lchild = s3                    # 右旋 s3绑到c左孩子if s3:                           # 反着链回去s3.parent = cg.rchild =cc.parent = g                      # c一定存在# 左旋 结束后 s2属于P右  P变成G的左孩子 G为根s2 = g.lchildp.rchild = s2if s2:s2.parent = pg.lchild = p                      # P拽成G左孩子p.parent = g# 更新bf  3种情况  右重为负#情况1 key 插入到s3上if g.bf > 0:                      # 其实是 = 1,往右偏,key插到s3上p.bf = -1                     # 看图二c.bf = 0#情况2 key 插入到s2上elif g.bf < 0:p.bf = 0                      # 看图二c.bf = 1#情况3 s1\s2\s3\s4都为空,即插入的是gelse:                            # g.bf=0 插入的是gp.bf = 0c.bf = 0

    def rotate_left_right(self, p, c):  # 左旋-右旋 反着来,镜面问题# 把r和l转过来,s3变成s2,-1变成1 镜面翻转g = c.rchilds2 = g.lchildc.rchild =  s2                  # s2拿下来 挂到c右if s2:s2.parent = cg.lchild = cc.parent = gs3 = g.rchild                   # 右旋p.lchild = s3                   # s3拿下来 挂到p左if s3:s3.parent = pg.rchild = pp.parent = g# 更新bfif g.bf < 0:                    # key插到g左 (s2上) 因为小于所以 = -1,与上相反p.bf = 1c.bf = 0elif g.bf > 0:p.bf = 0c.bf = -1else:                           # 插入的是gp.bf = 0c.bf = 0

# ---------右旋-左旋、左旋-右旋-----------------------------------------

# ---------右旋-左旋、左旋-右旋-----------------------------------------def insert_no_rec(self, val):   # 重写,覆盖父类pass

#78 AVL:插入

#注:从一个节点开始 如果插入操作是从左子树传进来的 ,那么bf - 1; 从右子树传来,bf + 1
#注:传递 从下到上到根 , 但是 一但中间有一个元素 bf变成0,那么 不再传递,不再更新  (因为虽插入,但左右高度不变)
#注:bf更新规律:插入节点后,从它的父亲开始,传递bf的变化 沿着树 往上走 (左子树来的 -1,右子树来的 +1)。但从某个节点开始,如果bf 变成0,那么传递 停止
#注:传递过程中 一旦有一个节点bf = 2 或 -2,树就不平衡。策略:把该子树 调整为平衡 4种情况 4个旋转操作。旋转节后后,子树 根节点 bf=0,就不传递
from bst import BiTreeNode, BST# 继承,复用代码
class AVLNode(BiTreeNode):    # 节点类def __init__(self, data):BiTreeNode.__init__(self, data)self.bf = 0            # 数据域 存balance factor(插入时会用)class AVLTree(BST):def __init__(self, li=None):BST.__init__(self, li) # 调用BST的构造函数# 4个旋转
#---------左旋、右旋-----------------------------------------def rotate_left(self, p, c):    # 左旋 p、c都是节点,右右s2 = c.lchild               # s1、s3都没有变p.rchild = s2               # s2给pif s2:                      # 判断s2是否空s2.parent = pc.lchild = pp.parent = c# 更新balance factor 定义右边重 就是负p.bf = 0                    # 如果有删除操作,代码错误c.bf = 0return cdef rorate_right(self, p, c):   # 右旋,左左情况s2 = c.rchild               # 只有 s2变p.lchild = s2if s2:s2.parent = pc.rchild = pp.parent = cp.bf = 0                    # 插入操作正确,删除时代码错误c.bf = 0return c
#---------左旋、右旋-----------------------------------------
#注:需要返回旋转之后的根,插入操作 将新节点 与它的上一级 连# ---------右旋-左旋、左旋-右旋-----------------------------------------def rotate_right_left(self, p, c):  # 右旋左旋g = c.lchild                     # 多了g# s2、s3本来属于G# 旋转结束 s2属于P s3属于C G为小根s3 = g.rchildc.lchild = s3                    # 右旋 s3绑到c左孩子if s3:                           # 反着链回去s3.parent = cg.rchild =cc.parent = g                      # c一定存在# 左旋 结束后 s2属于P右  P变成G的左孩子 G为根s2 = g.lchildp.rchild = s2if s2:s2.parent = pg.lchild = p                      # P拽成G左孩子p.parent = g# 更新bf  3种情况  右重为负#情况1 key 插入到s3上if g.bf > 0:                      # 其实是=1,往右偏,key插到s3上p.bf = -1                     # 看图二c.bf = 0#情况2 key 插入到s2上elif g.bf < 0:p.bf = 0                      # 看图二c.bf = 1#情况3 s1\s2\s3\s4都为空,即插入的是gelse:                            # g.bf=0 插入的是gp.bf = 0c.bf = 0return gdef rotate_left_right(self, p, c):  # 左旋-右旋 反着来,镜面问题# 把r和l转过来,s3变成s2,镜面翻转g = c.rchilds2 = g.lchildc.rchild =  s2                  # s2拿下来 挂到c右if s2:s2.parent = cg.lchild = cc.parent = gs3 = g.rchild                   # 右旋p.lchild = s3                   # s3拿下来 挂到p左if s3:s3.parent = pg.rchild = pp.parent = g# 更新bfif g.bf < 0:                    # key插到g左 (s2上) 因为小于所以=-1,与上相反p.bf = 1c.bf = 0elif g.bf > 0:p.bf = 0c.bf = -1else:                           # 插入的是gp.bf = 0c.bf = 0return g
# ---------右旋-左旋、左旋-右旋-----------------------------------------
# 注:需要返回旋转之后的根,插入操作 将新节点 与它的上一级 连#----------------插入 操作----------------------------------------------def insert_no_rec(self, val):   # 重写,覆盖父类# 步骤1、和BST一样,先插入 (二叉搜索树) 代码复制过来p = self.rootif not p:  # 空树self.root = AVLNode(val)    # 变成根returnwhile True:if val < p.data:if p.lchild:p = p.lchildelse:  # 左孩子不存在p.lchild = AVLNode(val)p.lchild.parent = pnode = p.lchild # node 存储的就是插入的节点break           # 跳出循环elif val > p.data:if p.rchild:p = p.rchildelse:p.rchild = AVLNode(val)p.rchild.parent = pnode = p.rchild # node 存储的就是插入的节点break           # 跳出循环else:   # val == p.data  尽量不允许2个相同的key插入在树里return# 步骤2、更新balance factorwhile node.parent:          # 保证node.parent不空,即一直到根# 看插入从左孩子/右孩子来的if node.parent.lchild == node:   # 传递是从左子树来的,左子树更沉了# 更新node.parent的bf -= 1if node.parent.bf < 0:  # 原来node.parent.bf == -1, 更新后变成-2# node.parent左沉时  2种情况 对应:右旋、左旋右旋# 做旋转# 看node哪边沉, node左边沉 右旋;node右边沉 左旋右旋x = node.parent # 旋转前的子树的根g = node.parent.parent  # 为了连接旋转之后的子树。存 根节点的上一级if node.bf > 0: # bf=+1 node右边沉,左旋右旋n = self.rotate_left_right(node.parent, node)   # 注意传的参数else:           # 左边沉, 右旋n = self.rorate_right(node.parent, node)# 记得:把n和g连起来    n是旋转后的子树 根。连接操作写在 所有情况后 ,减少代码量elif node.parent.bf > 0:  # 原来node.parent.bf = 1,更新之后变成0node.parent.bf = 0  # 只需要把它的bf = 0break               # 一但 某节点bf 变成0  变化就不再传递else:                     # 原来node.parent.bf = 0,更新之后变成-1# 需要向上传递 但不需要旋转node.parent.bf = -1node = node.parent  # 循环继续 往上走一层continue           # 连接操作放到最后else:                            # 传递是从右子树来的,右子树更沉了# 更新node.parent.bf += 1     3种情况if node.parent.bf > 0:  # 原来node.parent.bf == 1, 更新后变成2# 做旋转# 看node哪边沉      node右边沉(右右):左旋 ;node左边沉(右左):右左g = node.parent.parent  # 为了连接旋转之后的子树x = node.parent # 旋转前的子树的根if node.bf < 0: # node左边沉:右左  node.bf = -1n = self.rotate_right_left(node.parent, node)else:           # node右边沉:左旋  node.bf = 1n = self.rotate_left(node.parent, node)# 记得连起来elif node.parent.bf < 0: # 原来node.parent.bf = -1,更新之后变成0node.parent.bf = 0  # 更新break               # 是0后,不需要传递了else:                    # 原来node.parent.bf = 0,更新之后变成1node.parent.bf = 1node = node.parent  # 往上看,不需要旋转continue# 注:3种情况中,第1种 bf更新写在了旋转函数里,所以这里不需要更新# 注:这棵子树已经平衡了,是正负1的 ,走下一层continue 连起来,是0的 结束# 连接旋转后的子树      g跟n连起来 ,g是 上一级,n是子树 新根n.parent = gif g:   # g不是空 (涉及到根节点,都得判断是否为空)# 判断 连到左还是右if x == g.lchild: # x (旋转前 子树的根)是g原来的孩子 (原来的左孩子)g.lchild = nelse:                       # g原来的孩子x 是 右孩子g.rchild = nbreak                       # 调整完成后,新的树的根 bf 肯定是0,就不需要继续了else:   # 说明n是根节点,调整的是根节点  (g是空时)self.root = nbreak
# ----------------插入 操作----------------------------------------------

插入操作精简代码

    def insert_no_rec(self, val):# 1. 和BST一样,插入p = self.rootif not p:  # 空树self.root = AVLNode(val)returnwhile True:if val < p.data:if p.lchild:p = p.lchildelse:  # 左孩子不存在p.lchild = AVLNode(val)p.lchild.parent = pnode = p.lchild # node 存储的就是插入的节点breakelif val > p.data:if p.rchild:p = p.rchildelse:p.rchild = AVLNode(val)p.rchild.parent = pnode = p.rchildbreakelse:   # val == p.datareturn# 2. 更新balance factorwhile node.parent:  # node.parent不空if node.parent.lchild == node: # 传递是从左子树来的,左子树更沉了#更新node.parent的bf -= 1if node.parent.bf < 0: # 原来node.parent.bf == -1, 更新后变成-2# 做旋转# 看node哪边沉g = node.parent.parent # 为了连接旋转之后的子树x = node.parent  # 旋转前的子树的根if node.bf > 0:n = self.rotate_left_right(node.parent, node)else:n = self.rotate_right(node.parent, node)# 记得:把n和g连起来elif node.parent.bf > 0: # 原来node.parent.bf = 1,更新之后变成0node.parent.bf = 0breakelse: # 原来node.parent.bf = 0,更新之后变成-1node.parent.bf = -1node = node.parentcontinueelse: # 传递是从右子树来的,右子树更沉了#更新node.parent.bf += 1if node.parent.bf > 0:  # 原来node.parent.bf == 1, 更新后变成2# 做旋转# 看node哪边沉g = node.parent.parent # 为了连接旋转之后的子树x = node.parent  # 旋转前的子树的根if node.bf < 0: # node.bf = 1n = self.rotate_right_left(node.parent, node)else:   # node.bf = -1n = self.rotate_left(node.parent, node)# 记得连起来elif node.parent.bf < 0: # 原来node.parent.bf = -1,更新之后变成0node.parent.bf = 0breakelse: # 原来node.parent.bf = 0,更新之后变成1node.parent.bf = 1node = node.parentcontinue# 链接旋转后的子树n.parent = gif g: # g不是空if x == g.lchild:g.lchild = nelse:g.rchild = nbreakelse:self.root = nbreak
tree = AVLTree([9,8,7,6,5,4,3,2,1])tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)
#结果为  前序序列,中序序列
# 6,4,2,1,3,5,8,7,9,
# 1,2,3,4,5,6,7,8,9,

#79 AVL树应用与数据结构总结

#注:二叉搜索树(二叉排序树):比它小的在左边,比它大的在右边。根据这个特性,查找元素,顺着一棵树查 logn 查找速度
#注:如果二叉树 插入是 9 8 7 6 5 4 3 2 1 ,严重偏斜。所以搞了 AVL树,自平衡的树。balance factor 平衡因子 0 正负1 平衡
#注:如果插入一个数,导致了不平衡:从它的父亲 往上找,找到第一个 不平衡的节点,通过旋转(4种情况),旋转后平衡,再来下一次插入
###### 二叉搜索树扩展应用 -- B树
# B树(B-Tree):B树是一棵自平衡的多路搜索树。常用于数据库的索引。
#注:跟AVL树 不同的一点是:AVL树是二叉的,它是多叉的

#注:蓝的节点 ,就是我们 插入的数字。图示 是 三叉的B树 (或者说 三路的B树)
#注:一个节点上 存了2个值,分成3路。如果 分5路,需要存4个值,比第一个值小的去 第一路……
#注:主要用途 : 数据库的索引
#注:数据库 最常见的索引:2种   第1种:哈希表,对主键做哈希,根据这个哈希去查。 第2种:B树,充分利用硬盘块,每个东西是一块
#注:B树 升级的  B+树,大同小异,原理一样

#80 贪心算法

###### 贪心算法
#   ·贪心算法(又称贪婪算法)是指,在对问题求解时,总是做 出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
#   ·贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要会判断一个问题能否用贪心算法来计算。### 找零问题
#   ·假设商店老板需要找零 n元钱,钱币的面额有:100元、50元、 20元、5元、1元,如何找零使得所需钱币的数量最少?         #注:即 张数最少#注:这就是贪心的,要保证 钱币数量最少,先从最大面额找,最大面额找不开了,换下一面,再找,再找不开,再找……

代码

t = [100, 50, 20, 5, 1]  # 多少种钱币def change(t, n):        # t是货币面额类型,n是要找的钱m = [0 for _ in range(len(t))]   # 各个张数 0 0 0 0 0# 假设t都倒序排好了 (没排好的话,t.sort倒序排)for i, money in enumerate(t):m[i] = n // money   # n 整除 money,张数n = n % money       # n 还剩多少,取余 剩下的钱return m, n            # for循环结束后,n可能还剩下一点没找开print(change(t, 376))
#结果为
# ([3, 1, 1, 1, 1], 0)
#注:100的3张,其他面额各1张,最后剩0块钱

贪心 思想:当前最多的,先让100的找完了后 剩下的最少,尽可能先让 大面值的 拿走,这就是贪心

08_Python算法+数据结构笔记-二叉搜索树查询/删除-AVL树旋转/插入/应用-贪心算法相关推荐

  1. 数据结构 - 从二叉搜索树说到AVL树(一)之二叉搜索树的操作与详解(Java)

    二叉搜索树(Binary Search Tree),简称BST,顾名思义,一颗可以用于搜索的二叉树.BST在数据结构中占有很重要的地位,一些高级树结构都是其的变种,例如AVL树.红黑树等,因此理解BS ...

  2. 数据结构与算法 整理笔记---二叉搜索树

    二叉搜索树 查找问题是计算机中非常重要的基础问题 二分查找法 对于有序数组,才能使用二分查找法(排序作用) public class BinarySearch {public static int b ...

  3. 【数据结构与算法】之深入解析“把二叉搜索树转换为累加树”和“从二叉搜索树到更大和树”的求解思路与算法示例

    一.题目要求 ① 把二叉搜索树转换为累加树 给出二叉搜索树的根节点,该树的节点值各不相同,请将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 no ...

  4. 【数据结构与算法基础】二叉搜索树和平衡二叉树

    写在前面 今天学习在排序和查找中都很有用的特殊二叉树,平衡二叉树和搜索二叉树. 相关代码实现已上传至Github:data_structure/Tree/ 1.二叉搜索树(Binary Search ...

  5. 数据结构之二叉搜索树(BST)

    数据结构之二叉搜索树(BST) 1. 二叉搜索树定义 二叉搜索树(Binary Search Tree),又名二叉排序树(Binary Sort Tree). 二叉搜索树是具有有以下性质的二叉树: ( ...

  6. 数据结构:二叉搜索树(BST)的基本操作

    概述 学习过数据结构的童鞋都应该知道,对树的操作是一些最基本的技能(本文是对后面要写B树.B-树.B+树的一个前导,已经熟悉的朋友可以跳过了).而在树结构中,二叉树又是最基础的.虽然这些知识是比较基础 ...

  7. 算法导论习题—二叉搜索树、红黑树、区间树

    算法基础习题-二叉搜索树.红黑树.区间树 1.二叉搜索树: 2.红黑树: 3.区间树: 1.二叉搜索树: 设 T T T是一棵二叉搜索树,其关键字互不相同;设 x x x是一个叶结点, y y y为其 ...

  8. 【数据结构】二叉搜索树的实现

    目录 一.二叉搜索树的概念 二.二叉搜索树的中序遍历用于排序+去重 三.二叉搜索树的查找 1.查找的非递归写法 2.查找的递归写法 四.二叉搜索树的插入 1.插入的非递归写法 2.插入的递归写法 五. ...

  9. 数据结构:二叉搜索树(BST)全部基本操作

    #include <stdio.h> #include <stdlib.h>typedef struct node {int data;struct node* left;st ...

最新文章

  1. linux 笔记 一
  2. 影像组学视频学习笔记(37)-机器学习模型判断脑卒中发病时间(文献报告)、Li‘s have a solution and plan.
  3. linux shell rman删除归档_Oracle备份恢复必会--rman迁移,附实验步骤介绍
  4. 关于多线程Thread.Stop()破坏原子性
  5. 20. Valid Parentheses 有效的括号
  6. Python函数中的变量作用域
  7. Ubuntu系统备份和恢复
  8. Vue项目构建后通过Nginx/SpringBoot/Express/Egg发布
  9. POSIX 线程详解
  10. Django 路由系统
  11. 关于Executors.newFixedThreadPool何时创建新线程
  12. 超级实用且不花哨的js代码大全 (六) ----代码判断,幻灯片播放器,广告效果
  13. 有道词典【输入式翻页】
  14. AD20—PCB总结
  15. 蓝牙键盘常用快捷键记录
  16. sm专用计算机是啥意思,计算机CPU的主频代表的是什么意思
  17. 不小心删除JDK文件夹,无法重新安装该怎么办*
  18. Vue 拦截器 多接口调用 实现唯一loding效果 解决同步接口调用loding闪动
  19. python报告的创新点_课题研究中的拟创新点是什么
  20. datax 高级_DATAx上海峰会-百格活动

热门文章

  1. 蓝桥杯 算法训练 Cowboys DP
  2. if条件句和switch条件句
  3. 如何删除vue手脚架创建的自定义配置
  4. java csv 数组_使用csv文件的输入填充结构数组
  5. svn 代码迁移到git
  6. 十余年软件开发经历,经验总结和程序一览(涉及Socket、WPF、vc++、CAD、图像、GIS)...
  7. FOT再次上涨,中国市场需求不断上升,牛市真来了?
  8. ETH基于POA的环境搭建
  9. 【计算机操作系统基本概念】
  10. 开发零成本 :En-Tan-Mo黄皮书解读之开发者篇