Python3学习实战——顺序结构实现完全二叉树
前言
本笔记仅个人认知和见解,水平有限,还请见谅。
如有错误,还请指出,若有想法,欢迎共享!
本文实例是学习实践,实现目的但不是最优方法,欢迎探讨。
文章目录
实例:完全二叉树的创建与访问
1.要求
实现一个简单的完全二叉树,并封装完全二叉树的基本操作
2.什么是二叉树(Binary Tree)
一颗二叉树是结点的有限集合,这个集合可以为空,也可以由一个根节点加上左子树和右子树的二叉树构成。每个节点最多有两个子树,并且子树有左右之分,不可颠倒。
一个二叉树如果每层的节点都达到最大值,则称为满二叉树,总结点数为 2 k − 1 2^k-1 2k−1。
约定从上自下,自左向右从根节点开始编号,现有一颗深度为k的二叉树,有n个结点。当且仅当其每一个结点与深度为k的满二叉树中1-n编号一致时,称为完全二叉树。
换而言之,一颗最后一层仅留下左边连续结点且其余层均达到最大值的二叉树为完全二叉树。
3.如何储存二叉树
顺序结构
顺序结构存储就是利用序列储存,一般适用于完全二叉树和满二叉树。同时注意空结点要留空,否则无法模拟树。这样的储存在物理上是序列但是在逻辑上是二叉树。
链状结构
用链指示元素的逻辑关系,通常需要指定左子树和右子树,也有的指定父子树。利用这种方法,树状结构明显,逻辑关系清晰。
4.顺序结构实现完全二叉树
要求
实现完全二叉树的创建,读取,遍历,修改操作,并封装二叉树的基本操作。由于没有提出二叉树的用途,故制作的完全二叉树。同时实现层遍历,先序遍历,中序遍历和后序遍历。
构思
顺序结构实现完全二叉树类分二叉树创建,读取,遍历,修改四个方法。其中创建方法应该在类实例化的时候执行,构建一个完全二叉树。读取方法使用户以层和个的方式读取某层某个结点,也可以用编号获取指定的二叉树值。然后是遍历,遍历列表即可。最后是修改,先通过读取的方法得到值,再赋予指定的新值。
程序代码
#BinaryTree.py
class BinaryTree:
'''
指定层数为k,初始化执行层数指定和信息输入
然后为前面2^(k-1)个结点赋值
'''
def __init__(self,k=2):
self.k=k
self.len=0
self.BTList=[]
for i in range(pow(2,self.k)-1):
self.len+=1
print('\n位置:',self.XYorN(self.len),'值:',end='')
UserInput = input()
if(UserInput.isdecimal()):#判断用户输入是否为纯数字
UserInput = eval(UserInput)
elif(UserInput == ''):#用户空输入为空,判断是否合法
if(i+1 > pow(2,(self.k-1))):
break
else:
del self.BTList
break
self.BTList.append(UserInput)
#判断是不是用户异常输入导致创建失败
#成功就返回二叉树,失败提醒用户
try:
self.BTList
except AttributeError:
print('创建失败:用户终止')
else:
print('创建成功:层数{}'.format(self.k))
self.len-=1
def XYorN(self,arg1,arg2=None):
'''
结点编号转换器
仅输入一个数字是编号转结点
输入两个数字是结点转编号
约定根结点为1,编号从上到下自左到右
'''
if arg2==None:
x,y=0,0
n=arg1
for i in range(self.k-1,-1,-1):
if n > (pow(2,i)-1):
x=i+1
y=n-(pow(2,i)-1)
return (x,y)
else:
x,y=arg1,arg2
n=(pow(2,x-1)+y)
return n-1
#搜索和修改二叉树结点
def SoFBT(self,arg1,arg2=None,Num=None):
'''
用第几层第几个来搜索某个结点的值
也可以用位置编号搜索
'''
if arg2 == None:
if Num == None:
return self.BTList[arg1-1]
else:
self.BTList[arg1-1]=Num
else:
if Num == None:
return self.BTList[self.XYorN(arg1,arg2)-1]
else:
self.BTList[self.XYorN(arg1,arg2)-1]=Num
#列表的位置要-1
#寻找左子叶
def LeftL(self,Node):
xy = list(self.XYorN(Node+1))
xy[0] += 1
xy[1] *= 2
return self.XYorN(xy[0],xy[1]-1)-1
#层遍历二叉树
def ReadBT(self):
for i in range(self.len):
print('位置',self.XYorN(i+1),'值:',self.BTList[i])
#先序遍历二叉树
def PreorderBT(self,Node=0):
'''
先读根结点,再读根节点左边的结点
再度根结点右边的结点
默认从根节点开始
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
self.PreorderBT(self.LeftL(Node))#寻找左子叶
self.PreorderBT(self.LeftL(Node)+1)#寻找右子叶
#中序遍历二叉树
def InorderBT(self,Node=0):
'''
先读左子叶,在读根结点,最后读右子叶
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
self.InorderBT(self.LeftL(Node))#寻找左子叶
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
self.InorderBT(self.LeftL(Node)+1)#寻找右子叶
#后序遍历二叉树
def BackorderBT(self,Node=0):
'''
先遍历左子叶,再遍历右子叶,最后读根
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
self.BackorderBT(self.LeftL(Node))#寻找左子叶
self.BackorderBT(self.LeftL(Node)+1)#寻找右子叶
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
程序输出
>>> bt=BinaryTree(4)
位置: (1, 1) 值:1
位置: (2, 1) 值:a
位置: (2, 2) 值:b
位置: (3, 1) 值:1a
位置: (3, 2) 值:1b
位置: (3, 3) 值:1c
位置: (3, 4) 值:1d
位置: (4, 1) 值:α
位置: (4, 2) 值:β
位置: (4, 3) 值:γ
位置: (4, 4) 值:Δ
位置: (4, 5) 值:
创建成功:层数4
>>> bt.ReadBT()#层遍历
位置 (1, 1) 值: 1
位置 (2, 1) 值: a
位置 (2, 2) 值: b
位置 (3, 1) 值: 1a
位置 (3, 2) 值: 1b
位置 (3, 3) 值: 1c
位置 (3, 4) 值: 1d
位置 (4, 1) 值: α
位置 (4, 2) 值: β
位置 (4, 3) 值: γ
位置 (4, 4) 值: Δ
>>> bt.SoFBT(arg1=5,Num='起飞')#利用编号修改指定结点
>>> bt.PreorderBT()#先序遍历
位置: (1, 1) 值: 1
位置: (2, 1) 值: a
位置: (3, 1) 值: 1a
位置: (4, 1) 值: α
位置: (4, 2) 值: β
位置: (3, 2) 值: 起飞
位置: (4, 3) 值: γ
位置: (4, 4) 值: Δ
位置: (2, 2) 值: b
位置: (3, 3) 值: 1c
位置: (3, 4) 值: 1d
>>> bt.InorderBT()#中序遍历
位置: (4, 1) 值: α
位置: (3, 1) 值: 1a
位置: (4, 2) 值: β
位置: (2, 1) 值: a
位置: (4, 3) 值: γ
位置: (3, 2) 值: 起飞
位置: (4, 4) 值: Δ
位置: (1, 1) 值: 1
位置: (3, 3) 值: 1c
位置: (2, 2) 值: b
位置: (3, 4) 值: 1d
>>> bt.BackorderBT()#后序遍历
位置: (4, 1) 值: α
位置: (4, 2) 值: β
位置: (3, 1) 值: 1a
位置: (4, 3) 值: γ
位置: (4, 4) 值: Δ
位置: (3, 2) 值: 起飞
位置: (2, 1) 值: a
位置: (3, 3) 值: 1c
位置: (3, 4) 值: 1d
位置: (2, 2) 值: b
位置: (1, 1) 值: 1
创建完全二叉树
class BinaryTree:
'''
指定层数为k,初始化执行层数指定和信息输入
然后为前面2^(k-1)个结点赋值
'''
def __init__(self,k=2):
self.k=k
self.len=0
self.BTList=[]
for i in range(pow(2,self.k)-1):
self.len+=1
print('\n位置:',self.XYorN(self.len),'值:',end='')
UserInput = input()
if(UserInput.isdecimal()):#判断用户输入是否为纯数字
UserInput = eval(UserInput)
elif(UserInput == ''):#用户空输入为空,判断是否合法
if(i+1 > pow(2,(self.k-1))):
break
else:
del self.BTList
break
self.BTList.append(UserInput)
#判断是不是用户异常输入导致创建失败
#成功就返回二叉树,失败提醒用户
try:
self.BTList
except AttributeError:
print('创建失败:用户终止')
else:
print('创建成功:层数{}'.format(self.k))
self.len-=1
首先在实例化时获取用户期望的二叉树层数k(k≥2),然后询问用户输入,创建前面 2 k − 1 − 1 2^{k-1}-1 2k−1−1个结点,如果中间有数据输入为空则创建无效。在最后一层有 2 k − 1 2^{k-1} 2k−1个结点,这一层开始如果输入None则停止获得用户输入,二叉树创建完成。这一部分应该在二叉树创建的时候就调用,所以利用构造函数作为初始化方法。当然也可以将后面的内容独立作一个方法。
编号转化方法
def XYorN(self,arg1,arg2=None):
'''
结点编号转换器
仅输入一个数字是编号转结点
输入两个数字是结点转编号
约定根结点为1,编号从上到下自左到右
'''
if arg2==None:
x,y=0,0
n=arg1
for i in range(self.k-1,-1,-1):
if n > (pow(2,i)-1):
x=i+1
y=n-(pow(2,i)-1)
return (x,y)
else:
x,y=arg1,arg2
n=(pow(2,x-1)+y)
return n-1
这个部分主要为了实现多态,不论输入编号还是输入层&个的格式,都可以正确得到用户想要的二叉树结点。
两种写法分别为,第x层第y个或编号为n(约定自上而下自左向右由根结点开始编号,根结点为1)。两种表达方式的转换公式: n = 2 x − 1 + y n=2^{x-1}+y n=2x−1+y。
搜索和修改二叉树方法
def SoFBT(self,arg1,arg2=None,Num=None):
'''
用第几层第几个来搜索某个结点的值
也可以用位置编号搜索
'''
if arg2 == None:
if Num == None:
return self.BTList[arg1-1]
else:
self.BTList[arg1-1]=Num
else:
if Num == None:
return self.BTList[self.XYorN(arg1,arg2)-1]
else:
self.BTList[self.XYorN(arg1,arg2)-1]=Num
这部分可以实现多态,如果只输入第一个参数,则认为输入编号,查询指定编号的结点。如果输入两个参数,则认为是层&个格式,转化为编号到列表中搜索指定值并返回。需要注意的是,我们约定了根节点为1,但实际物理存储中是从0开始的,所以位置是编号-1。
然后通过获取用户是否给予Num值,判断用户是否需要修改这个结点的值。如果是修改值,则函数会执行操作而返回None。
层遍历完全二叉树方法
def ReadBT(self):
for i in range(self.len):
print('位置',self.XYorN(i+1),'值:',self.BTList[i])
和遍历列表一样遍历即可,配合位置转化方法可以将实现同时输出位置和值。
先序遍历、中序遍历、后续遍历
#寻找左子叶
def LeftL(self,Node):
xy = list(self.XYorN(Node+1))
xy[0] += 1
xy[1] *= 2
return self.XYorN(xy[0],xy[1]-1)-1
#先序遍历二叉树
def PreorderBT(self,Node=0):
'''
先读根结点,再读根节点左边的结点
再度根结点右边的结点
默认从根节点开始
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
self.PreorderBT(self.LeftL(Node))#寻找左子叶
self.PreorderBT(self.LeftL(Node)+1)#寻找右子叶
#中序遍历二叉树
def InorderBT(self,Node=0):
'''
先读左子叶,在读根结点,最后读右子叶
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
self.InorderBT(self.LeftL(Node))#寻找左子叶
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
self.InorderBT(self.LeftL(Node)+1)#寻找右子叶
#后序遍历二叉树
def BackorderBT(self,Node=0):
'''
先遍历左子叶,再遍历右子叶,最后读根
'''
try:
if self.BTList[Node] == None:
return
except IndexError:
return
self.BackorderBT(self.LeftL(Node))#寻找左子叶
self.BackorderBT(self.LeftL(Node)+1)#寻找右子叶
print('位置:',self.XYorN(Node+1),'值:',self.BTList[Node])
用序列的方式储存二叉树,查找左子叶和右子叶是比较麻烦的事情,好在我已经提供了编号和层&个转化的方法,只需要稍加处理,就可以用层和个的方式查找左子叶和右子叶了。接着是三种遍历方式,三种遍历方式大同小异,都是利用了递归的思想。先判断所需要读取的结点是否在序列之外,如果在序列外或是空结点则退出函数。然后进入递归,递归的时候我只需要考虑读取顺序,比如先序遍历,我先读根结点,再读左子叶,再读右子叶。我不关心后面怎么读,我只关心前面两层,剩下的交给递归处理。递归会帮我去完成子结点的读取,如果子节点有子节点,递归还是会帮我完成子节点的子节点的读取……
优化方向
虽然程序实现了完全二叉树的简单操作方法,但是还有很大的优化空间。对于一些情况,可以将二叉树的创建放在对象实例化上,在构造函数用*arg获得前面的所有结点,并且自动创建一个若干层的二叉树。这样你只需要给予值,而不用关系我的二叉树要有多少层,同时可以用None代表二叉树的空结点,即使不是完全二叉树也可以完成,但是要警惕顺序输入时空空结点有子结点的情况。现在这个程序的遍历操作也偏向展示值,而不是返回值。