python应用——AVL实现商品库动态平衡

一.题目要求

    根据AVL对商品进行动态平衡问题

二.设计思路

应用设计思路:

为方便对商品库问题进行模拟,同时在运行时提示用户“输入新的搜索词”及提示管理员是否需要增删库中商品,每修改一次AVL树,输出一次当前的AVL树,形式为:商品名称 树中的高度 左节点名称 右节点名称(如缺少左/右节点,用None代替),初始设置为:

 

库中默认商品包含在number_list中,建立的AVL树为treeoldwords中包含已入库商品名及其对应搜索频次,newwords包含未入库但被检索的商品名。Searchnum为用户搜索次数。用户搜索达到一定次数时需删除低频次商品,将触发机制设置为delnum,每搜索delnum次商品,检查是否要删除某些商品,并提示管理员。N=2表示每delnum次搜索,若每个商品搜索次数未达到N,提示管理员。W=3表示若新商品累计搜索次数(按照论文的思想,未限定时间,只累计新商品总的搜索次数)达到3次时,提示管理员是否需要添加该商品。

删除操作设计思路:

   将删除的情况分类讨论:

l 删除的点不存在

l 删除叶节点

l 删除节点只有左孩子

l 删除节点只有右孩子

l 删除节点左右孩子都有,此时需要找到左子树最右节点,往上提

三.程序主体

# coding=utf-8
class AVLTreeNode():#AVL树节点包含的信息:关键值key,父节点p,左节点left,右节点right,高度height
    
def __init__(self, key):
        self.key = key
        self.p = None
        
self.left = None
        
self.right = None
        
self.height = 0

def get_height(node):#获得节点高度,若节点为空返回高度-1
    
return node.height if node else -1


def get_maximum(node):#找到当前点向下最右节点,即该子树最大值
    
temp_node = node
    while temp_node.right:
        temp_node = temp_node.right
    return temp_node


def get_minimum(node):#找到当前点向下最左节点,即该子树最小值
    
temp_node = node
    while temp_node.left:
        temp_node = temp_node.left
    return temp_node


def preorder_tree_walk(node):#迭代输出关键值和高度,顺序为当前点,左点,右点
    
if node:
        if node.left and node.right:
            print(node.key, node.height,node.left.key,node.right.key)
        elif node.left :
            print(node.key, node.height, node.left.key,'None')
        elif node.right:
            print(node.key, node.height, 'None',node.right.key)
        else:
            print(node.key, node.height, 'None','None')
        preorder_tree_walk(node.left)
        preorder_tree_walk(node.right)


def left_left_rotate(tree, node):#即LL,左高插入左边
    # 先将 node 和 node_left 之间及其左右节点赋值 (node_left.left node.right 保持不变)
    
node_left = node.left
    node.left = node_left.right
    node_left.right = node
    if not node.p:
        tree.root = node_left
        node_left.p = None
    elif
node == node.p.left:
        node.p.left = node_left
        node_left.p = node.p
    elif node == node.p.right:
        node.p.right = node_left
        node_left.p = node.p
    node.p = node_left
    while node:
        node.height = max(get_height(node.left), get_height(node.right)) + 1
        node = node.p


def right_right_rotate(tree, node):#即RR,右高插入右边
    
node_right = node.right
    node.right = node_right.left
    node_right.left = node
    if not node.p:
        tree.root = node_right
        node_right.p = None
    elif
node == node.p.left:
        node.p.left = node_right
        node_right.p = node.p
    elif node == node.p.right:
        node.p.right = node_right
        node_right.p = node.p
    node.p = node_right
    while node:
        node.height = max(get_height(node.left), get_height(node.right)) + 1
        node = node.p


def left_right_rotate(tree, node):#即LR,左高插入右边
    
right_right_rotate(tree, node.left)
    left_left_rotate(tree, node)


def right_left_rotate(tree, node):#即Rl,右高插入左边
    
left_left_rotate(tree, node.right)
    right_right_rotate(tree, node)


class AVLTree(object):
    def __init__(self):
        self.root = None

    def
search(self, key):
        if not self.root:#空树
            
return None
        else
:
            return self._search(key)

    def _search(self, key):#从根节点向下查找,返回key ==start.key的Start节点
        
start = self.root
        while start:
            if key < start.key:
                start = start.left
            elif key > start.key:
                start = start.right
            else:
                return start
        return None

    
############################################################插入
    
def insert(self, node):
        temp_root = self.root
        temp_node = None#初始化插入点父根
        # 找到要插入的父节点(temp_node)
        
while temp_root:#temp_root相当于一个中间寄存器
            
temp_node = temp_root
            if node.key < temp_node.key:
                temp_root = temp_root.left
            elif node.key > temp_node.key:
                temp_root = temp_root.right
            else:
                raise KeyError("Error!")##要插入的点已在树中,则报错

        # 如果父节点为空 则说明这是一个空树 把 root 赋值即可
        
if not temp_node:
            self.root = node
        elif node.key < temp_node.key:#父节点非空,且应该在temp_node左节点处插入
            
temp_node.left = node
            node.p = temp_node#.p代表父节点
            
temp_node.height = max(get_height(temp_node.left), get_height(temp_node.right)) + 1#更新被插入的父节点的高度
            
temp_p = temp_node.p#几个点的顺序:temp_p——temp_node——node(新插入的点)
            
while temp_p:#不断向上更新高度
                
temp_p.height = max(get_height(temp_p.left), get_height(temp_p.right)) + 1
                temp_p = temp_p.p
        elif node.key > temp_node.key:#与上面的if过程对称,是在temp_node右节点处插入
            
temp_node.right = node
            node.p = temp_node
            temp_node.height = max(get_height(temp_node.left), get_height(temp_node.right)) + 1
            temp_p = temp_node.p
            while temp_p:
                temp_p.height = max(get_height(temp_p.left), get_height(temp_p.right)) + 1
                temp_p = temp_p.p
        self.fixup(node)

    def fixup(self, node):#对新插入/删除的点进行fixup调整树
        
if node == self.root:#从空树插入一个点后不需要调整
            
return
        while
node:#从插入点往上找失衡位置,
            
if get_height(node.left) - get_height(node.right) == 2:#左高插入左边后,左子树高度高了2
                
if node.left.left:
                    left_left_rotate(self, node)#LL调整
                
else:
                    left_right_rotate(self, node)
                break       #使用break后调整完即结束了
            
elif get_height(node.right) - get_height(node.left) == 2:#右子树高度高了2
                
if node.right.right:
                    right_right_rotate(self, node)
                else:
                    right_left_rotate(self, node)
                break
            
node = node.p
############################################################删除
    
def delete(self, key):
        temp_node = self.root
        while temp_node:  #从根节点向下定位需要删除的位置
            
if key > temp_node.key:
                temp_node = temp_node.right
            elif key < temp_node.key:
                temp_node = temp_node.left
            else:
                break  #找到需要删除的位置(key == temp_node.key)即停止
        
if not temp_node:#没有需要删除的点
            
return False
        
##################################
        
elif temp_node.left and temp_node.right is None:  # 只有左子树
            
if temp_node == temp_node.p.left:  # 要删除的点是其父节点的左孩子
                
temp_node.p.left =temp_node.left
                temp_node.left.p=temp_node.p
            else:
                temp_node.p.right =temp_node.left
                temp_node.left.p = temp_node.p
            temp_node =temp_node.left
        elif temp_node.left is None and temp_node.right :  # 只有右子树
            
if temp_node == temp_node.p.left:  # 要删除的点是其父节点的左孩子
                
temp_node.p.left =temp_node.right
                temp_node.right.p=temp_node.p
            else:
                temp_node.p.right =temp_node.right
                temp_node.right.p=temp_node.p
            temp_node =temp_node.right
        elif temp_node.left is None and temp_node.right is None:  # 删除的是叶节点
            
if temp_node == temp_node.p.left:  # 要删除的点是其父节点的左孩子
                
temp_node.p.left =None
            else
:  # 替换要删除的点
                
temp_node.p.right =None
            
temp_node =temp_node.p
        #####################################



        
elif temp_node.left and temp_node.right:  # 要删除的点左右孩子都有
            # 找到左子树中的最大值往上提
            
node_max = get_maximum(temp_node.left)  # 删除节点左子树最右节点
            # 第一步:#处理左子树中的最大值以下的节点
            
if node_max.left:  # 如果左树高提左树最大值,且该最大值有左子树(根据定义,最大子节点一定没有右孩子)。提最大值时需要修改下其余的连接关系
                
if node_max == node_max.p.left:  # 要最大点是其父节点的左孩子
                    
node_max.left.p = node_max.p
                    node_max.p.left = node_max.left
                else:
                    node_max.p.right = node_max.left
                    node_max.left.p = node_max.p
            else:
                if node_max == node_max.p.left:  # 要最大点是其父节点的左孩子
                    
node_max.p.left = None
                else
:
                    node_max.p.right = None
            
# 第二步:用node_min替换temp_node,处理向下的连接
            
node_max.right = temp_node.right
            temp_node.right.p = node_max
            if temp_node.left != node_max:
                node_max.left = temp_node.left
                if temp_node.left != None:
                    temp_node.left.p = node_max
            else:
                node_max.left = None
            
# 第三步:处理node_min向上的连接
            
temp_node_new = node_max.p  # 当前节点
            
if temp_node.p:  # 需要删除的点有父节点
                
if temp_node == temp_node.p.left:  # 要删除的点是其父节点的左孩子
                    
temp_node.p.left = node_max  # 用左最大替换要删除的位置
                    
node_max.p = temp_node.p
                else:  # 替换要删除的点
                    
temp_node.p.right = node_max
                    node_max.p = temp_node.p
            else:  # 要删除的点是根节点
                
self.root = node_max
                node_max.p = None
            
temp_node = temp_node_new  # 当前节点
        
temp_p = temp_node
        while temp_p:
            temp_p.height = max(get_height(temp_p.left), get_height(temp_p.right)) + 1
            temp_p = temp_p.p
        self.fixup(temp_node)

四.主函数

#################################################################
def main():
    number_list = ('HE','NJ','FS','YJ','XZ')
    tree = AVLTree()
    oldwords={}
    newwordes={}
    searchnum=0#用以累计到delnum
    
delnum=10#为调试方便,设定每输入10次会删去树中搜索次数小于阈值N的商品
    
N=2#被删除的阈值
    
W=3#自定义新词添加的阈值,每有3词新词被检索,则提示是否增加到树中
    
for number in number_list:
        node = AVLTreeNode(number)
        tree.insert(node)
        oldwords[number]=1
    print('按照论文数据给出的AVL树')
    preorder_tree_walk(tree.root)
    #######################################根据搜索做出增删AVL树
    
while True:
        a = input("输入搜索词:")
        searchnum+=1
        if a in oldwords:#树中已含节点有被删除的风险
            
oldwords[a]+=1
            ###################################################################################

        
else:#新搜索词累计到一定次数W则有机会添加到AVL树中
            
if a not in newwordes:
                newwordes[a]=1
            else:
                newwordes[a]+=1
                if newwordes[a]>=W:#有希望被添加到树中,提醒管理员
                    
print('最新热搜商品:',a)
                    b = input('是否添加到库中(请输入yes/no):')
                    if b=='yes':
                        newproduct=AVLTreeNode(a)
                        tree.insert(newproduct)
                        oldwords[a]=W
                        newwordes.pop(a)
                        preorder_tree_walk(tree.root)
                    else:#管理员选择暂不添加
                        
newwordes.pop(a)

        deloldwords=[]
        if searchnum==delnum:#搜索次数达到设定值,提示是否删去某些词
            
searchnum=0
            for word in oldwords:
                frequency=oldwords[word]
                oldwords[word] = 0
                if frequency<N:
                    print('低搜索商品:',word)
                    z = input('是否从库中删除(请输入yes/no):')
                    if z=='yes':
                        tree.delete(word)
                        deloldwords.append(word)
                        preorder_tree_walk(tree.root)
            for p in deloldwords:
                oldwords.pop(p)
            print('目前库中商品:',oldwords)


if __name__ == '__main__':
    main()

五.运行结果

C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe C:/Users/Administrator/PycharmProjects/untitled1/AVL.py

按照论文数据给出的AVL

HE 2 FS XZ

FS 0 None None

XZ 1 NJ YJ

NJ 0 None None

YJ 0 None None

输入搜索词:HE

输入搜索词:FS

输入搜索词:AA

输入搜索词:XZ

输入搜索词:YJ

输入搜索词:AA

输入搜索词:YJ

输入搜索词:AA

最新热搜商品: AA

是否添加到库中(请输入yes/no):yes

HE 2 FS XZ

FS 1 AA None

AA 0 None None

XZ 1 NJ YJ

NJ 0 None None

YJ 0 None None

输入搜索词:XZ

输入搜索词:YJ

低搜索商品: NJ

是否从库中删除(请输入yes/no):yes

HE 2 FS XZ

FS 1 AA None

AA 0 None None

XZ 1 None YJ

YJ 0 None None

目前库中商品: {'HE': 0, 'FS': 0, 'YJ': 0, 'XZ': 0, 'AA': 0}

输入搜索词:

六.结果分析

初始商品有:'HE','NJ','FS','YJ','XZ'

初始AVL树为:

 

后不断输入新的搜索词,新搜索词AA累计搜索次数达到W=3次时,提示管理员是否需要新加,键入yes后新的搜索树中包含AA

 

  累计搜索次数达到delnum=10次时,检查搜索次数不到N=2的商品(初始化AVL树时,默认搜索次数已有1,不占后面10次搜索的名额),管理员同意删除后该商品从库中删除:

 

猜你喜欢

转载自blog.csdn.net/weixin_41819299/article/details/80840216