一.题目要求
根据AVL对商品进行动态平衡问题
二.设计思路
应用设计思路:
为方便对商品库问题进行模拟,同时在运行时提示用户“输入新的搜索词”及提示管理员是否需要增删库中商品,每修改一次AVL树,输出一次当前的AVL树,形式为:商品名称 树中的高度 左节点名称 右节点名称(如缺少左/右节点,用None代替),初始设置为:
库中默认商品包含在number_list中,建立的AVL树为tree,oldwords中包含已入库商品名及其对应搜索频次,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次搜索的名额),管理员同意删除后该商品从库中删除: