二叉树(python描述)中的那些坑 你踩过多少?

目录

二叉树的定义

二叉树的性质

二叉树的实现

一、列表之列表:不新建类,而是借助python自带的list来实现,如

二、通过节点与引用的方式

二叉树中的那些坑

二叉树的应用


二叉树的定义

二叉树(Binary Tree)是n(n≥0)个结点所构成的集合,它或为空树(n=0);或为非空树,对于非空树T:
(1)有且仅有一个称之为根的结点;
(2)除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树。

二叉树的性质

  1. 性质1 在二叉树的第i层上至多有2i−1个结点(i≥1)。
  2. 性质2 深度为k的二叉树至多有2k−1个结点(k≥1)。
  3. 性质3 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
  4. 性质4 具有n个结点的完全二叉树的深度为\left \lfloor log2n\right \rfloor +1           ( #向下取整)
  5. 性质5 如果对一棵有n个结点的完全二叉树(其深度为[\left \lfloor log2n\right \rfloor +1)的结点按层序编号(从第1层到第\left \lfloor log2n\right \rfloor +1层,每层从左到右),则对任一结点i(1≤i≤n),有

        (1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲PARENT(i)是结点\left \lfloor i /2 \right \rfloor
        (2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(i)是结点2i。
        (3)如果2i+1>n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1。

二叉树的实现

二叉树用python实现一般有两种:

一、列表之列表:不新建类,而是借助python自带的list来实现,如

 可以表示为[1,[2,[4],[5]],[3,[6],[7]], 即通过列表的嵌套来表示二叉树

二、通过节点与引用的方式

通过创建类来表示二叉树。

不同的书介绍了两种不同的创建二叉树类的方法:

1、

class TreeNode:
    def __init__(self,val,left = None, right = None):
        self.val = val
        self.left = left
        self.right = right
class BinaryTree:
    def __init__(self):
        self.root = None

通过两个类(节点类、树类)来表示二叉树,优点是通过往树中插入节点来构建二叉树,理解起来不容易卡壳,但是很容易掉进一个坑,后续写实现方法的时候,一不小心就把两个类搞混淆,然后各种报TreeNode类没有root属性,BinaryTree类没有val属性的错误,让人抓狂。

2、第二种是用得比较多的方法

直接用一个类来表示二叉树,把一个没有左右儿子的节点也看作是一棵树

class BinaryTree:
    def __init__(self,val = None,left = None,right = None):
        '''构造一棵树或者说一个节点'''
        self.val = val 
        self.left = left 
        self.right = right

这种方式的缺点是在插入节点时实际是插入一棵子树,不太容易理解

基础方法:

方法名        描述
get_root_val取节点的值
set_root_val设置节点的值
get_left_child取左儿子
get_right_child取右儿子
insert_left插入左儿子
insert_right插入右儿子
pre_order前序遍历
#----------------基础方法-----------------
    def get_root_val(self):
        if self.val:
            return self.val
        return None
    
    def set_root_val(self,data):
        self.val = data 
    
    def get_left_child(self):
        if self.left:
            return self.left
        else:
            return None
        
    def get_right_child(self):
        if self.right:
            return self.right
        else:
            return None
        
    def insert_left(self,new_node):
        '''插入左子树'''
        if  self.left == None:
            self.left = BinaryTree(new_node)
        else:
            t = BinaryTree(new_node)
            t.left = self.left
            self.left = t 
    
    def insert_right(self,new_node):
        '''插入右子树'''
        if  self.right == None:
            self.right = BinaryTree(new_node)
        else:
            t = BinaryTree(new_node)
            t.right = self.right
            self.right = t
        
    def pre_order(self):
        '''前序遍历'''
        if self:
            print(self.val)
            if self.left:
                self.left.pre_order()
            if self.right:
                self.right.pre_order()
                
    def  in_order(self):
        '''中序遍历'''
        if self:
            if self.left:
                self.left.in_order()
            print(self.val)
            if self.right:
                self.right.in_order()
                
    def post_order(self):
        '''后序遍历'''
        if self:
            if self.left:
                self.left.post_order()
            if self.right:
                self.right.post_order()
            print(self.val)

因为二叉树本身是与一个递归的定义,所以写二叉树的方法的时候难免会经常遇到使用递归算法。递归调用的时候就会涉及到参数的传递问题

比如,最初写出的前序遍历的代码是这个样子的

    def pre_order(self):
        '''前序遍历'''
        if self:
            print(self.val)
            self.left.pre_order()
            self.right.pre_order()

但是一调用就会出错,错误是NoneTyp没有pre_order方法.分析了一下,原因是递归到叶子节点的时候,因为儿子为空,就没有pre_order方法了,一种解决方法是像上面的添加一个if判断.还有一种方法是把前序遍历的函数写在类的外面,然后把树作为参数来传递完成递归.

def pre_order(tree):
    if tree:
        print(tree.val)
        pre_order(tree.left)
        pre_order(tree.right)

同时这种把方法写在二叉树类外面的逻辑对于像递归学得不好的人来说,在解决一些问题的时候非常有用.后面我会创建一个解决问题的类来封装一些二叉树的常见算法.

进阶方法:

 def insert(self,item):
        '''树中插入元素 (按层次遍历插入\非递归)'''
        new_tree = BinaryTree(item)
        if self.val == None:
            self.val = item
            return
        queue = [self]
        while queue:
            prevent = queue.pop(0)
            if prevent.left == None:
                prevent.left = new_tree
                return
            else:
                queue.append(prevent.left)
            if prevent.right == None:
                prevent.right = new_tree
                return
            else:
                queue.append(prevent.right)

    def sumVal(self):
        ''''求一棵树的所有节点值的和'''
        res = 0
        q = [self]
        while q:
            prevent = q.pop(0)
            if prevent:
                res += prevent.val 
            if prevent.left:
                q.append(prevent.left)
            if prevent.right:
                q.append(prevent.right)
        return res
    
    def leafNodeCount(self):
        '''统计叶子结点的个数'''
        if not self: # 空树
            return 0 
        elif not self.left and not self.right: # 左右子树均为空
            return 1 
        elif self.left and not self.right: # 左非空 右空
            return self.left.leafNodeCount()
        elif not self.left and self.right:  # 左空 右非空
            return self.right.leafNodeCount() 
        else: # 左右均非空
             return self.left.leafNodeCount() + self.right.leafNodeCount() 
             
    def twoBifurcateNodeCount(self):
        '''统计有两个分支的结点的个数'''
        res = 0
        q = [self]
        while q:
            prevent = q.pop(0)
            
            if prevent.left and prevent.right:
                res += 1
            if prevent.left:
                q.append(prevent.left)
            if prevent.right:
                q.append(prevent.right)
        return res
        
    def oneBifurcateNodeCount(self):
        '''统计有一个分支的结点的个数'''
        res = 0
        q = [self]
        while q:
            prevent = q.pop(0)
            
            if prevent.left and not prevent.right:
                res += 1
            if not prevent.left and  prevent.right:
                res += 1
            if prevent.left:
                q.append(prevent.left)
            if prevent.right:
                q.append(prevent.right)
        return res
    
    def nodeCount(self):
        '''节点个数'''
        res = 0
        q = [self]
        while q:
            prevent = q.pop(0)
            if prevent:
                res += 1
            if prevent.left:
                q.append(prevent.left)
            if prevent.right:
                q.append(prevent.right)
        return res

其中通过队列来完成一些二叉树的算法是必须掌握的用来避开递归坑的基本操作.

最后是二叉树的常见的一些算法,通过一个叫solution的类来封装

#创建解决方案类(一些二叉树的典型算法)   
class Solution:
    
    def isSamTree(self,p:BinaryTree,q:BinaryTree):
        '''判断两棵树相等'''
        if not p and not q:
            return True
        elif not p or not q:
            return False
        elif p.val != q.val:
            return False
        else:
            left = self.isSamTree(p.left,q.left)
            right = self.isSamTree(p.right,q.right)
            return left and right
    
    def isSubTree(self,t1,t2):
        '''判断t1是否包含t2
        如果不包含:返回False
        如果包含:返回True'''
        
        # 递归算法失败
        # if self.isSamTree(t1,t2):
        #     return True 
        # if t1.left:
        #     self.isSubTree(t1.left,t2)
        # if t1.right:
        #     self.isSubTree(t1.right,t2)
            
        # 非递归算法
        if self.isSamTree(t1,t2):
            return True 
        queue = [t1]
        while queue:
            t = queue.pop(0)
            if self.isSamTree(t.left,t2):
                return True
            else:
                if t.left: # 此处判断是否存在左子树,不加会报错
                    queue.append(t.left)
            if self.isSamTree(t.right,t2):
                return True
            else:
                if t.right:
                    queue.append(t.right)
        return False
        
    def depth(self,tree):
        '''递归求树的深度'''
        if not tree:
            return 0 
        else:
            return max(self.depth(tree.get_left_child()),\
            self.depth(tree.get_right_child())) + 1
            
        
    def zuixiaohe(self,tree):
        ''' 求最小子树和'''
        sum_val_dict = {}  # 为返回最小子树和对应的根节点  用字典{和:根的值}的方式来保存
        q = [tree]
        while q:
            prevent = q.pop(0)
            sum_val_dict.update({prevent.sumVal():prevent.val})
            if prevent.left:
                q.append(prevent.left)
            if prevent.right:
                q.append(prevent.right)
        print(sum_val_dict)
        return min(sum_val_dict)
        
        
    def restoreBinTree(self,preOrderList,inOderList):
        '''
        通过前序\中序遍历二叉树的序列重建二叉树
        preOrderList:  前序序列的列表
        inOderList  :中序序列的列表
        '''
        if not preOrderList or not inOderList or len(preOrderList) != len(inOderList):
            return None
        if len(preOrderList) == 1:
            return BinaryTree(preOrderList[0])
        else:
            node = preOrderList.pop(0) 
            root = BinaryTree(node)
            
            pos = inOderList.index(node)
            nextLfetInOrderList = inOderList[:pos]
            lengh = len(nextLfetInOrderList)
            nextLeftPreOrderList = preOrderList[:lengh]
            nextRightInOrderList = inOderList[pos+1:]
            nextRightPreOrderList = preOrderList[lengh:]
            
            root.left = self.restoreBinTree(nextLeftPreOrderList,nextLfetInOrderList)
            root.right = self.restoreBinTree(nextRightPreOrderList,nextRightInOrderList)
            return root
            
    def mirrorBinTree(self,tree):
        '''返回一颗二叉树的镜像'''
        if not tree or not tree.left or not tree.right:
            return
        else:
            tree.left,tree.right = tree.right,tree.left
        self.mirrorBinTree(tree.left)
        self.mirrorBinTree(tree.right)
        return tree
     
    def levelTraversal(self,tree):
        '''层次遍历二叉树'''
        if not tree:
            return
        queue = [tree]
        while queue:
            cur = queue.pop(0)
            print(cur.val)
            if cur.left:
                queue.append(cur.left)
            if cur.right:
                queue.append(cur.right)
    
    def BinaryTreeWidth(self,tree):
        '''求二叉树的宽度'''
        tmp = 0 # 局部宽度
        maxw = 0  #全局最大宽度
        if not tree:
            return 0
        else:
            
            front = 1 #  表示对头指针
            rear = 1 # 表示对尾指针
            last = 1 # 同层最右节点在队列中的位置
            queue = [tree]  # 根节点入队
            
            while front <= last:
                cur = queue.pop(0)
                front += 1
                tmp += 1 
                if cur.left:
                    queue.append(cur.left)
                    rear += 1
                if cur.right:
                    queue.append(cur.right)
                    rear += 1
                if front > last: #同一层结束
                    last = rear
                    maxw,tmp = tmp , 0
            return maxw
    
    def WPL(self,tree,d):
        '''WPL:二叉树的带权路径长度 = 所有叶子节点的带权路径长度之和\
        = 左子树的所有戒子节点的带权路径之和 + 右子树的所有叶子节点的带权路径之和
        假设叶子节点val 为该节点的权重 
        则: 叶子节点的带权路径长度 = val * 该节点的深度
        d表示深度 初始时: d = 0 表示根节点'''
        if not tree:
            return 0 
        if not tree.left and not tree.right:
            return tree.val * d
        else:
            return self.WPL(tree.left,d+1) + self.WPL(tree.right,d+1)
               
    def allPath(self,tree): # 没明白什么意思
        '''输出二叉树根节点到每一个叶子节点的路径
        前序遍历二叉树,非叶子节点加入path中,
        遇到叶子节点则加入path后打印,同时重置path'''
        path = [] 
        
        def allPath2(tree):
            if tree:
                path.append(tree.val)
                if not tree.left and not tree.right:
                    print(path)
                    # path =[]
                if tree.left:
                    allPath2(tree.left)
                if tree.right:
                    allPath2(tree.right)
        allPath2(tree)
        
    def isFullBinTree(self,tree):
        '''判断一棵树是否为满二叉树'''
        nodecount = tree.nodeCount() 
        deep = self.depth(tree)
        if nodecount < 2**deep - 1:
            return False
        return True
        
    def isCompleteBinTree(self,tree):
        '''判断是否完全二叉树'''
        if not tree or (not tree.left and not tree.right):
            return False
        queue = [tree]
        flag = False #变量用来标记一个状态是否发生\
        '''(只要当前节点的左孩子和右孩子都为空\
        或者左孩子不为空,右孩子为空时,这个状态就发生,
        只要发生了这个状态,以后访问到的节点必须都是叶节点)'''
        while queue:
            cur = queue.pop(0)
            if((flag and (cur.left or cur.right)) or (cur.left==None and cur.right!=None)): #这些判断条件是所有的不满足是完全二叉树的条件。条件一(第二个||前面的条件):上述的状态已经发生,但是当前访问到的节点不是叶节点(有左孩子或者右孩子)。条件二:当前节点有右孩子,没有左孩子
                return False 
            if cur.left: # 左孩子不为空,加入到队列中去
                queue.append(cur.left)
            if cur.right:# 右孩子不为空,加入到队列中去
                queue.append(cur.right)
            if((cur.left != None and cur.right == None ) or (not tree.left and not tree.right)):# 这个if语句就是判断状态是否要发生
                flag = True
        return True
        
    def createBinSearchTree(self,sequece):
        '''创建一颗二叉查找树'''
        binSearchTree = BinaryTree()
        if sequece  == []:
            return binSearchTree
        root_val = sequece.pop(0)
        binSearchTree.set_root_val(root_val)
        
        def insertByBinSearchTree(tree,item): # 按照二叉查找树的规律插入元素
            if item == tree.val:
                raise ValueError
            if  item < tree.val:
                if not tree.left:
                    tree.insert_left(item)
                    return
                else:
                    insertByBinSearchTree(tree.left,item)
            if item > tree.val:
                if not tree.right:
                    tree.insert_right(item)
                    return
                else:
                    insertByBinSearchTree(tree.right,item)
        
        for item in sequece:
            insertByBinSearchTree(binSearchTree,item)
        return binSearchTree

ref:

1\严蔚敏<数据结构>>

2\李冬梅<<数据结构>>习题解析

3\戴维.瑞德\约翰.策勒<<数据结构和算法python和c++语言描述>>

4\张玲玲<<python算法详解>>

5\布拉德利.米勒\戴维.拉努姆<<python数据结构与算法分析>>

6\判断完全二叉树时参考的这个大神的:

(14条消息) 判断一个二叉树是否为完全二叉树_齐鲁工业大学--栾琪的博客-CSDN博客_判断二叉树是否为完全二叉树


ps:35岁学python,也不知道为了啥

肝了两个星期,第一次csdn发文,感谢您的阅读!!!

祝君晚安!!!

二叉树中的那些坑

如上:

递归的坑

子树不存在的坑

二叉树的应用

还有赫夫曼树,红黑树 等写不动了...待续吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值