Python3学习实战——创建链状二叉树

本文介绍了如何使用Python3创建链状二叉树,并封装了包括创建、访问、修改结点、层遍历、先序遍历、中序遍历、后序遍历等基本操作。文章通过孩子表示法实现二叉树,详细展示了程序代码和运行示例,强调了链状结构在表达二叉树逻辑关系上的优势,并与顺序结构进行了对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python3学习实战——创建链状二叉树

前言

本笔记仅个人认知和见解,水平有限,还请见谅。

如有错误,还请指出,若有想法,欢迎共享!

本文实例是学习实践,实现目的但不是最优方法,欢迎探讨优化方案。


友情链接:Python3学习实战——创建完全二叉树

上文只实现了完全二叉树的顺序结构储存,并未实现二叉树的链状结构储存,现在通过链状结构再次尝试。同时上文简略介绍二叉树一些基本知识,故本文不再介绍。

1.要求

创建链状二叉树,并封装二叉树的基本操作(创建,访问,修改结点内容,层遍历,先序遍历,中序遍历,后序遍历)。

2.链状储存二叉树

一颗二叉树(Binary Tree)是结点的有限集合,这个集合可以为空,也可以由一个根节点加上左子树和右子树的二叉树构成。每个节点最多有两个子树,并且子树有左右之分,不可颠倒。使用链状结构储存二叉树,即一个二叉树结点需要指定其与其他二叉树结点之间的逻辑关系。常见的链状结构储存二叉树的方法有孩子表示法,双亲表示法(让每个结点记住其父结点),孩子兄弟表示法(一个结点指定其左子节点和其兄弟结点)等。

本文用的是孩子表示法,即一个结点指定左右两个子节点。

3.链状结构实现二叉树

构思

链状结构实现二叉树,首先要有一个结点对象,还要有一个树对象链接这些结点对象,并提供一些方法实现二叉树的创建,访问,修改结点内容,层遍历,先序遍历,中序遍历,后序遍历等操作。由于没有指定二叉树的实际用途,故制作一个展示向的二叉树,即打印二叉树为主。本文约定根结点为第一层,从上到下层数递增,每层左边第一个结点为本层一号结点,从左到右结点编号递增,如果过程中存在空结点,同样计数。

程序完整代码

#BinaryTree2.py

#结点单位
class Node:
    def __init__(self,item):
        self.Data=item
        self.left=None
        self.right=None

    #查询结点内容
    def Read(self):
        return self.Data
    
    
#树结构
class Tree:
    def __init__(self):
        self.root=None
        self.nodek=0    #记录层数

    #创建树
    def CreatTree(self,item):
        if self.root == None:
            self.root = Node(item)
            
        local = self.root
        list1 = [local]
        list2 = []
        k = 1           #记录层
        
        while True:
            k += 1
            print('层',k)
            count = 0
            for i in list1:
                
                #读值
                if i != None:
                    
                    count += 1
                    print('第 {} 个:'.format(count),end='')
                    item = input()
                    if item not in [None,'']:
                       i.left = Node(item)
                    else:
                        i.left = None
                        
                    count += 1
                    print('第 {} 个:'.format(count),end='')
                    item = input()
                    if item not in [None,'']:
                        i.right = Node(item)
                    else:
                        i.right = None
                        
                    list2.append(i.left)
                    list2.append(i.right)
                else:
                    count += 2
                    list2.append(None)
                    list2.append(None)
            list1 = list2
            list2 = []
            if list1.count(None) == pow(2,k-1):
                break
            else:
                print('\n')
        
        self.nodek = k-1
        return None
    
    def SearchNode(self,nodex,nodey,local):
        '''
        选择往右的话,y除以2
        选择往左的话,不变
        上升一层,x-1
        '''
        if nodex == 1 and nodey == 1:
            return local
        if nodey > (pow(2,nodex-1))/2:
            if local.right != None:
                local = local.right
                nodey -= (pow(2,nodex-1))/2
                nodex -= 1
                return self.SearchNode(nodex,nodey,local)
            else:
                return None
        else:
            if local.left != None:
                local = local.left
                nodex -= 1
                return self.SearchNode(nodex,nodey,local)
            else:
                return None

    #查询结点,如果item有输入,则是修改结点
    def FoundNode(self,nodex,nodey,item=None):
        local = self.root
        if item == None:
            return self.SearchNode(nodex,nodey,local)
        else:
            if local != None:
                local.Data = item
        
    #层遍历
    def LayerRead(self):
        local = self.root
        list1 = [local]
        list2 = []
        k = 0           #记录层
        while True:
            k += 1
            print('层',k)
            for i in list1:
                #读值
                if i != None:
                    print(i.Read(),end=' ')
                    list2.append(i.left)
                    list2.append(i.right)
                else:
                    print('×',end=' ')
                    list2.append(None)
                    list2.append(None)
                    
            list1 = list2
            list2 = []
            if list1.count(None) == pow(2,k):
                break
            else:
                print('\n',end='')
        
        self.nodek = k
        return None

    #先序遍历
    def Preorder(self):
        local = self.root
        self.PreorderBT(local)
        
    def PreorderBT(self,local):
        if local != None:
            print('值:',local.Read())
            self.PreorderBT(local.left)
            self.PreorderBT(local.right)
        else:
            return None

    #中序遍历
    def Inorder(self):
        local = self.root
        self.InorderBT(local)
        
    def InorderBT(self,local):
        if local != None:
            self.InorderBT(local.left)
            print('值:',local.Read())
            self.InorderBT(local.right)
        else:
            return None
        
    #后序遍历
    def Backorder(self):
        local = self.root
        self.BackorderBT(local)
        
    def BackorderBT(self,local):
        if local != None:
            self.BackorderBT(local.left)
            self.BackorderBT(local.right)
            print('值:',local.Read())
        else:
            return None

程序输出

>>> bt=Tree()
>>> bt.CreatTree(1)21 个:12 个:231 个:12 个:23 个:
第 4 个:441 个:12 个:
第 3 个:34 个:47 个:
第 8 个:551 个:
第 2 个:
第 5 个:
第 6 个:
第 7 个:
第 8 个:
第 15 个:
第 16 个:
#一层都输入空则结束获取
>>> bt.LayerRead()#层遍历1
12
1 23
1 2 × 44
1 × 3 4 × × × 5 
>>> bt.FoundNode(4,3)#查询节点
<__main__.Node object at 0x00000169CACE7730>
>>> bt.FoundNode(4,3).Read()#阅读结点的值
'3'
>>> bt.FoundNode(4,3,114514)#修改结点的值
>>> bt.FoundNode(4,3).Read()
114514
>>> bt.Preorder()#先序遍历
值: 1
值: 1
值: 1
值: 1
值: 2
值: 114514
值: 4
值: 2
值: 4
值: 5
>>> bt.Inorder()#中序遍历
值: 1
值: 1
值: 1
值: 114514
值: 2
值: 4
值: 1
值: 2
值: 4
值: 5
>>> bt.Backorder()#后序遍历
值: 1
值: 1
值: 114514
值: 4
值: 2
值: 1
值: 5
值: 4
值: 2
值: 1

数据结构

class Node:
    def __init__(self,item):
        self.Data=item
        self.left=None
        self.right=None

    #查询结点内容
    def Read(self):
        return self.Data
    
class Tree:
    def __init__(self):
        self.root=None
        self.nodek=0    #记录层数

数据分为结点类和树类,结点类有属性Data,即储存的数据;left,左子节点;right,右子节点,同时提供了一个方法Read(),可以读取当前节点的内容。树类则初始化一个根结点,设为空,并且初始化一个属性nodek,用于记录层数。

二叉树创建与层遍历

    #创建二叉树
    def CreatTree(self,item):
        if self.root == None:
            self.root = Node(item)
            
        local = self.root
        list1 = [local]
        list2 = []
        k = 1           #记录层
        
        while True:
            k += 1
            print('层',k)
            count = 0
            for i in list1:
                
                #读值
                if i != None:
                    
                    count += 1
                    print('第 {} 个:'.format(count),end='')
                    item = input()
                    if item not in [None,'']:
                        i.left = Node(item)
                    else:
                        i.left = None
                        
                    count += 1
                    print('第 {} 个:'.format(count),end='')
                    item = input()
                    if item not in [None,'']:
                        i.right = Node(item)
                    else:
                        i.right = None
                        
                    list2.append(i.left)
                    list2.append(i.right)
                else:
                    count += 2
                    list2.append(None)
                    list2.append(None)
            list1 = list2
            list2 = []
            if list1.count(None) == pow(2,k-1):
                break
            else:
                print('\n')
        
        self.nodek = k-1
        return None
    
    #层遍历
    def LayerRead(self):
        local = self.root
        list1 = [local]
        list2 = []
        k = 0           #记录层
        while True:
            k += 1
            print('层',k)
            for i in list1:
                #读值
                if i != None:
                    print(i.Read(),end=' ')
                    list2.append(i.left)
                    list2.append(i.right)
                else:
                    print('×',end=' ')
                    list2.append(None)
                    list2.append(None)
                    
            list1 = list2
            list2 = []
            if list1.count(None) == pow(2,k):
                break
            else:
                print('\n',end='')
        
        self.nodek = k
        return None


这部分做了相当多可视化,方便用户输入,所以说这个程序偏向展示。之所以将创建二叉树和层遍历放在一起,因为其方法很接近。

先介绍一下层遍历的方法:层遍历需要借助一个列表,站在上一层将下一层所有的节点按顺序储存在列表中,然后再遍历这个列表,即可实现层遍历。站在上一层的时候,一个结点会为列表写入左右两个子结点,如果当前结点为空,则也要为列表写入两个空结点,用于计算二叉树是否结束。需要注意的是,如果遍历到一整层结点都为空,则认为二叉树已经结束,结束循环并且刷新二叉树层数的属性。

创建二叉树则在层遍历的基础上,先判断根结点有没有赋值,如果根节点存在值,则跳过;如果不存在,则将函数中的Data属性赋值。然后按层遍历的方法为每一层赋值,规则是从上到下,从左到右。区别于层遍历的地方在于,打印操作变成了为左右子节点赋值的操作,然后判断二叉树是否结束由下一层是否全空变为这一层是否全空。如果全空,则认为创建完成。同时这样创建的二叉树可以重新构造,但是根节点不能直接利用方法修改。

查询或修改指定位置的结点

def SearchNode(self,nodex,nodey,local):
    '''
    选择往右的话,y除以2
    选择往左的话,不变
    上升一层,x-1
    '''
    if nodex == 1 and nodey == 1:
        return local
    if nodey > (pow(2,nodex-1))/2:
        if local.right != None:
            local = local.right
            nodey -= (pow(2,nodex-1))/2
            nodex -= 1
            return self.SearchNode(nodex,nodey,local)
        else:
            return None
    else:
        if local.left != None:
            local = local.left
            nodex -= 1
            return self.SearchNode(nodex,nodey,local)
        else:
            return None

#查询结点,如果item有输入,则是修改结点
def FoundNode(self,nodex,nodey,item=None):
    local = self.root
    if item == None:
        return self.SearchNode(nodex,nodey,local)
    else:
        if local != None:
            self.SearchNode(nodex,nodey,local).Data = item

查询指定位置时要牢记我们的约定,以免得到不想要的结果。

查询指定位置利用了递归,并且分为相对位置查找和绝对位置查找。相对位置查找是方法SearchNode(),需要指定层nodex和编号nodey,并且给予一个结点作为虚拟的根开始查找。而FoundNode()是绝对位置查找,同时兼具修改值的功能,参数为nodex,指定层;nodey,指定个;item,可选,用于指定修改的值,如果有输入则认为需要修改这个位置的值。

利用递归实现位置查找,本质上是左右子节点的选择。我们根据指定的层nodex和指定的个nodey判断选择左节点还是右节点。每次执行函数就执行一次左右结点的选择,然后根据选择后的结果再次判断选择左节点还是右结点,即利用递归。如果 n o d e y > 2 n o d e x − 1 2 nodey>\frac{2^{nodex-1}}2 nodey>22nodex1,即所需的结点在该层结点在该层的右半部分,则我们这一次操作要往右,然后将nodex减去1,即减少一层,将nodey减去所在层的一半,这是得到的就是选择一次后的位置,再次套用这个函数判断,直到达到该结点。如果所需结点在该层左半部分,则我们这一次操作向左,nodex要减去1而nodey无需减一。

先序遍历,中序遍历,后序遍历

#先序遍历
def Preorder(self):
    local = self.root
    self.PreorderBT(local)

def PreorderBT(self,local):
    if local != None:
        print('值:',local.Read())
        self.PreorderBT(local.left)
        self.PreorderBT(local.right)
    else:
        return None

#中序遍历
def Inorder(self):
    local = self.root
    self.InorderBT(local)

def InorderBT(self,local):
    if local != None:
        self.InorderBT(local.left)
        print('值:',local.Read())
        self.InorderBT(local.right)
    else:
        return None

#后序遍历
def Backorder(self):
    local = self.root
    self.BackorderBT(local)

def BackorderBT(self,local):
    if local != None:
        self.BackorderBT(local.left)
        self.BackorderBT(local.right)
        print('值:',local.Read())
    else:
        return None

三种遍历方式大同小异,都是利用递归的思想,只是执行的顺序不太一样。对于递归操作,我需要想的只有执行怎样重复的操作,然后实现其中一个操作,剩下的操作交给递归即可。比如先序遍历,我需要知道的是先读根结点,再读左节点,最后读右结点。读左节点的时候可能又有读根节点,读左节点,再度右节点的操作……我不需要关心怎么办,我只需要实现一次先序读取的操作,然后把先序读取完整过程交给递归,让递归自动完成所有操作,直到根节点为None停下。

4.总结

首先,本程序偏向展示,如果需要投入使用肯定需要修改一定的方法,但总体思路不会改变,且本程序基本实现上文提出的优化方向。如果还要优化,可以从算法上和表示上进一步优化程序,使程序更实用。在逻辑上可以挑战不利用递归的方法实现先序遍历,中序遍历,后序遍历,还有搜索结点位置,让自己可以写的程序更广泛。

5.链状结构与顺序结构小对比

相比于顺序结构,链状结构更能表达二叉树之间的逻辑关系,即更有“树”味。在空间上,链状结构和顺序结构各有优缺点:如果是一颗满二叉树或完全二叉树,则顺序结构更有优势,顺序结构方便查阅,同时由于满二叉树和完全二叉树中间没有数据间断,所以在储存空间上没有浪费,更加节约空间。而如果一颗二叉树有大量残缺,那么链状结构可能是不错的选择。链状结构的每一个结点需要额外储存左右子结点的位置,而顺序结构只需要在列表连续的空间储存相应的数据,虽然不能肯定链状结构的每一个结点都要比顺序结构的大(没有实际数据说明一个链状结构的结点对象比列表的一个位置要大,因为Python的列表实现的方式我尚不明确,不敢妄下定论),但是如果一颗二叉树残缺的部分很多,那么顺序结构将面临大量留空和逻辑错误两个问题,顺序结构带来的逻辑性不强将限制顺序结构作为二叉树的发挥。由于拟树需要,顺序结构表示二叉树必须要将空结点留白,这样才能很好地拟树,但是大量留白会浪费大量内存,这导致其在一定的程度下会比链状结构更占用内存(前提是这个链状结构的结点将每个不存在的结点的链接用None封起来,而不是创建结点但是储存一个None),如果二叉树很大,那浪费可能是指数增长的(一个浪费造成下一层两个浪费,下下层四个浪费……)。同时,顺序结构的二叉树没有显著的逻辑关系,如果在一个空结点后面接上两个结点也是很容易的,甚至不易察觉的,这会干扰树结构的运作,制作程序的时候可能要考虑更多类似的情况以避免序结构的错误逻辑,所以链状结构更适合用于制作不完全的二叉树,顺序结构制作不完全的二叉树前要仔细考虑空间和逻辑问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值