Python描述数据结构之二叉树篇

前言

  本篇章主要介绍二叉树,包括二叉树的定义、基本性质、存储结构及遍历,并用Python实现其创建及其遍历等操作。

1. 二叉树的定义

  与相似,二叉树 ( B i n a r y (Binary T r e e ) Tree) 也是 n ( n 0 ) n(n \geq 0) 个结点的有限集合,其特点是每个结点最多只有两棵子树,即二叉树中不存在度大于2的结点,而且其子树有左右之分(左子树与右子树),次序不能颠倒。所以根据定义,二叉树有以下5种基本形态:
  二叉树与度为2的有序树的区别是:
  (1) 度为2的树至少有3个结点,而二叉树可以为空;
  (2) 度为2的有序树的孩子的左右次序是相对另一个孩子而言的,若某个结点只有一个孩子,则这个孩子无需区分是左还是右,而二叉树就不一样了,它需要区分。

2. 特殊二叉树

2.1 满二叉树

  一棵高度为 h h ,且含有 2 h 1 2^h-1 个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点。满二叉树的叶子结点都集中在二叉树的最下层,除叶子结点外的每个结点的度都为2。

在这里插入图片描述
  由图可知,对于编号为 i i 的结点,若有双亲,则其双亲为 i 2 \lfloor \frac {i} {2}\rfloor ;若有左孩子,则左孩子为 2 i 2i ;若有右孩子,则右孩子为 2 i + 1 2i+1

2.2 完全二叉树

  一棵高度为 h h ,含有 n n 个结点的二叉树,当且仅当其每个结点都与高度为 h h 的满二叉树中编号为 1 1 ~ n n 的结点一一对应时,称为完全二叉树。

  完全二叉树就是对应相同高度的满二叉树缺失最下层最右边的一些连续叶子结点

在这里插入图片描述
  特点如下:
  (1) 若 i n 2 i \leq \lfloor \frac {n} {2}\rfloor ,则结点 i i 为分支结点,否则为叶子结点;
  (2) 叶子结点只可能在层次最大两层上出现。对于最大层次中的叶子结点,都依次排列在该层最左边的位置上;
  (3) 若有度为1的结点,则只可能有一个,且该节点只有左孩子而无右孩子;
  (4) 若按层序编号后,一旦出现某结点(假设编号为 i i )为叶子结点或只有左孩子,则编号大于 i i 的结点均为叶子结点;
  (5) 若 n n 为奇数,则每个分支结点只有左孩子和右孩子;若 n n 为偶数,则编号最大分支结点(假设编号为 n 2 \frac {n} {2} )只有左孩子,没有右孩子,其余分支结点左、右孩子都有。

2.3 二叉排序树

  左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字;左子树和右子树右各是一棵二叉排序树。

  关键字:数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。在介绍查找这部分时还会再说。

扫描二维码关注公众号,回复: 11596363 查看本文章

2.4 平衡二叉树

  树上任一结点的左子树和右子树的深度之差不超过1。

3. 基本性质

3.1 性质1

  性质1:在二叉树的第 i i 层上至多有 2 i 1 2^{i-1} 个结点( i 1 i \geq 1 );

  证明:当 i = 1 i=1 时, 2 i 1 = 2 0 = 1 2^{i-1}=2^0=1 ,即至多只有一个根结点;当 i = 2 i=2 时, 2 i 1 = 2 1 = 2 2^{i-1}=2^1=2 ,即至多只有两个结点;当 i = 3 i=3 时, 2 i 1 = 2 2 = 4 2^{i-1}=2^2=4 ,即至多只有四个结点;以此类推,由归纳假设可知这是一个公比为2的等比数列,所以第 i i 层上至多有 2 i 1 2^{i-1} 个结点。

3.2 性质2

  性质2:深度为 k k 的二叉树至多有 2 k 1 2^k-1 个结点( k 1 k \geq 1 );

  证明:深度为 k k 的二叉树的最大结点数为(等比数列求和)
i = 1 k ( i ) = i = 1 k 2 i 1 = 2 k 1 \sum_{i=1}^k(第i层上的最大结点数)=\sum_{i=1}^k2^{i-1}=2^k-1

3.3 性质3

  性质3:对任何一棵二叉树 T T ,如果其叶子结数为 n 0 n_0 ,度为2的结点数为 n 2 n_2 ,则 n 0 = n 2 + 1 n_0=n_2+1

  证明:假设度为0,1和2的结点数分别为 n 0 n_0 n 1 n_1 n 2 n_2 ,结点总数 n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 ;再看二叉树中的分支数,除根节点外,其余结点都有一个分支进入,设 B B 为分支总数,则 n = B + 1 n=B+1 ;由于这些分支是由度为1或2的结点射出的,所以又有 B = n 1 + 2 n 2 B=n_1+2n_2 ,于是可得 n 0 + n 1 + n 2 = n 1 + 2 n 2 + 1 n_0+n_1+n_2=n_1+2n_2+1 ,即 n 0 = n 2 + 1 n_0=n_2+1

3.4 性质4

  性质4:具有 n n 个结点的完全二叉树的深度为 log 2 ( n + 1 ) \lceil \log_2(n+1) \rceil log 2 n + 1 \lfloor \log_2n \rfloor+1

  证明:假设深度为 k k ,则根据性质2和完全二叉树的定义有
2 k 1 1 < n 2 k 1 2 k 1 n < 2 k 2^{k-1}-1<n\leq 2^k-1或2^{k-1}\leq n <2^k
可得 2 k 1 < n + 1 2 k 2^{k-1}<n+1\leq 2^k ,即 k 1 < log 2 ( n + 1 ) k k-1<\log_2(n+1)\leq k ,因为 k k 为正整数,所以 k = log 2 ( n + 1 ) k=\lceil \log_2(n+1) \rceil ,或者也可得 k 1 log 2 n < k k-1\leq \log_2n<k ,即 k = log 2 n + 1 k=\lfloor \log_2n \rfloor+1

3.5 性质5

  性质5:如果对一棵有 n n 个结点的完全二叉树的结点按层序编号,则对任一结点,有

  (1) 如果 i = 1 i=1 ,则结点 i i 是二叉树的根结点,无双亲结点;如果 i > 1 i>1 ,则其双亲结点的编号为 i 2 \lfloor \frac {i} {2} \rfloor ,即当 i i 是偶数时,其双亲结点的编号为 i 2 \frac {i} {2} ,它是双亲的左孩子;当 i i 是奇数时,其双亲结点的编号为 i 1 2 \frac {i-1} {2} ,它是双亲的右孩子。
  (2) 如果 2 i > n 2i>n ,则结点 i i 无左孩子,即结点 i i 为叶子结点;否则其左孩子的编号为 2 i 2i
  (3) 如果 2 i + 1 > n 2i+1>n ,则结点 i i 无右孩子;否则其右孩子的编号为 2 i + 1 2i+1

4. 存储结构

4.1 顺序存储结构

  用一组地址连续的存储单元自上而下、自左至右存储完全二叉树上的结点元素,即将完全二叉树上的结点存储在一维数组中。

在这里插入图片描述
  由此可见,这种顺序存储结构仅适用于完全二叉树,因为,在最坏的情况下,一个深度为 k k 且只有 k k 个结点单只树却需要长度为 2 k 1 2^k-1 的一维数组。

4.2 链式存储结构

  对于非完全二叉树来树,顺序存储的空间利用率较低,而且树越深,结点越多,浪费的空间更多。通常二叉树都采用链式存储结构,用链表结点来存储二叉树的每个结点,每个结点包括数据域data、左指针域lchild、右指针域rchild。结点定义如下:

class BiTreeLinkNode(object):
    def __init__(self, data='#'):
        self.data = data
        self.lchild = None
        self.rchild = None

  下图为常用的二叉链表的存储结构,实际上,在不同的应用中,还可以增加某些指针域,如增加指向父结点的指针parent后,变为三叉链表的存储结构。

在这里插入图片描述

5. 二叉树的遍历

在这里插入图片描述
  根据二叉树的定义,可将结点分为以下4种结点:

  • LR结点:有左子树和右子树的结点;
  • NR结点:无左子树但有右子树的结点;
  • LN结点:有左子树但无右子树的结点;
  • NN结点:无左子树且无右子树的结点。

  遍历二叉树就是以一定的规则将二叉树中的结点排列成一个线性序列,使得序列中的每个结点都有一个直接前驱和直接后继,当然,第一个和最后一个结点除外。
  空结点的值这里设置为#

5.1 先序遍历

  根结点 > --> 左子树 > --> 右子树

  上面那棵树通过先序遍历得到的先序序列为: A B D H I E C F J G ABDHIECFJG

5.1.1 递归法

  对于LR结点,先访问结点本身,再递归访问其左子树,然后递归访问其右子树,最后结束当前递归调用并返回至上一层;对于NR结点,由于其无左子树,所以访问根结点后就直接访问右子树,然后结束当前递归并返回至上一层;对于LN结点,由于其无右子树,所以就正常访问根结点后,然后在递归访问其左子树,最后结束当前递归调用并返回至上一层;对于NN结点,由于其无左子树和右子树,所以访问根结点后即可结束当前递归并返回至上一层。
  递归实现先序遍历:

    def PreOrder(self, RootNode):
        if RootNode:
            self.VisitBinaryTreeNode(RootNode)
            self.PreOrder(RootNode.lchild)
            self.PreOrder(RootNode.rchild)

5.1.1 非递归法

在这里插入图片描述
  看图说话,紫色箭头就是向下遍历,直至结点为NN结点,然后返回至上一层(红色的箭头),即当前结点的双亲结点,然后再遍历双亲结点的右孩子。其实递归操作也是如此,只不过递归的代码更简洁。
  非递归实现先序遍历:

    def PreOrderNoRecursive(self, RootNode):
        # 使用一个栈来记录访问过的二叉树的结点
        stackNode = []
        treeNode = RootNode
        while len(stackNode) > 0 or treeNode is not None:
            while treeNode is not None:
                # 向下遍历
                self.VisitBinaryTreeNode(treeNode)
                stackNode.append(treeNode)
                treeNode = treeNode.lchild
            if len(stackNode) > 0:
                # 返回至上一层结点
                treeNode = stackNode.pop()
                # 访问其右孩子
                treeNode = treeNode.rchild

5.2 中序遍历

  左子树 > --> 根结点 > --> 右子树

  上面那棵树通过中序遍历得到的中序序列为: H D I B E A J F C G HDIBEAJFCG

  中序遍历的第一个结点是二叉树最左侧的结点,最后一个结点是二叉树最右侧的一个结点。

5.2.1 递归法

  递归实现中序遍历:

    def InOrder(self, RootNode):
        if RootNode:
            self.InOrder(RootNode.lchild)
            self.VisitBinaryTreeNode(RootNode)
            self.InOrder(RootNode.rchild)

5.2.2 非递归法

  非递归实现中序遍历:

    def InOrderNoRecursive(self, RootNode):
        # 使用一个栈来记录未访问过的二叉树的结点
        stackNode = []
        treeNode = RootNode
        while len(stackNode) > 0 or treeNode is not None:
            while treeNode is not None:
                stackNode.append(treeNode)
                treeNode = treeNode.lchild
            if len(stackNode) > 0:
                treeNode = stackNode.pop()
                self.VisitBinaryTreeNode(treeNode)
                treeNode = treeNode.rchild

5.3 后序遍历

  左子树 > --> 右子树 > --> 根结点

  上面那棵树通过后序遍历得到的后序序列为: H I D E B J F G C A HIDEBJFGCA

5.3.1 递归法

  递归实现后序遍历:

    def PostOrder(self, RootNode):
        if RootNode:
            self.PostOrder(RootNode.lchild)
            self.PostOrder(RootNode.rchild)
            self.VisitBinaryTreeNode(RootNode)

5.3.2 非递归法

在这里插入图片描述

  后序遍历的非递归算法还有那么一丝丝的麻烦,根据上图的二叉树,这里的大致流程就是,如果当前结点有左孩子的话,我就一直遍历,所以就是 A > B > D > H A->B->D->H ,如果当前结点没有右孩子结点,就直接访问当前结点,然后将其双亲结点的 s t a t e state 标记为 T r u e True ,意味着双亲结点的左孩子结点已经被访问过。然后就返回到了结点 D D ,它有右结点 I I ,如果结点 I I 没有孩子结点的话就直接访问,有的话再来一次遍历,访问结点 I I 之后在访问结点 D D ,就这样一直遍历。

class TreeState(object):
    # 标记一个结点及其右孩子结点是否已被访问
    def __init__(self, Node, state):
        self.Node = Node
        self.state = state

  非递归实现后序遍历:

    def PostOrderNoRecursive(self, RootNode):
        # 使用一个栈来记录未访问过的二叉树的结点及其状态
        # 如果当前结点有左孩子, 来标记左孩子结点是否已经被访问过
        stackNode = []
        treeNode = RootNode
        while treeNode is not None:
            # 先跑到结点H
            tempNode = TreeState(treeNode, state=False)
            stackNode.append(tempNode)
            treeNode = treeNode.lchild
        while len(stackNode) > 0:
            treeNode = stackNode.pop()
            if treeNode.Node.rchild is None or treeNode.state:
                # 当前结点没有右孩子
                self.VisitBinaryTreeNode(treeNode.Node)
            else:
                # 当前结点有右孩子, 且还没有被访问
                stackNode.append(treeNode)
                treeNode.state = True
                treeNode = treeNode.Node.rchild
                while treeNode is not None:
                    tempNode = TreeState(treeNode, state=False)
                    stackNode.append(tempNode)
                    treeNode = treeNode.lchild

5.4 层次遍历

  从根结点开始,按结点所在层次从小到大,从左到右访问树中的每个结点。
  上面那棵树通过层次遍历得到的层次序列为: A B C D E F G H I J ABCDEFGHIJ
  层次遍历的流程就相对简单了,这里用到了一个通过collections库里面的deque类构建的双端队列,具体用法可参考我的这篇博客,层次遍历的代码如下:

    def LeverOrder(self, RootNode):
        from collections import deque
        queue = deque(maxlen=100)
        queue.append(RootNode)
        while len(queue) > 0:
            treeNode = queue.popleft()
            self.VisitBinaryTreeNode(treeNode)
            if treeNode.lchild is not None:
                queue.append(treeNode.lchild)
            if treeNode.rchild is not None:
                queue.append(treeNode.rchild)

6. 创建二叉树

  en,前面的遍历都已经介绍了,这都不是最重要的,最重要的是没有一棵二叉树怎么遍历???─━ _ ─━✧,下面就通过递归来创建一棵二叉树,建造的流程可参考代码注释,代码如下:

    def CreateBinaryTree(self, root=None, pos=0):
        """
        建立一个层次序列为ABCDEFGHI##J###的二叉树
        *****************A*****************
        ***********************************
        *********B***************C*********
        ***********************************
        *****D*******E*******F*******G*****
        ***********************************
        ***H***I***#***#***J***#***#***#***
        递归结束条件: pos>=15 and data='#' 然后回退到上一个结点, 下一部就是创建右子树
        0->1->3->7->15->16->8->17->18->4->9->10->2->5->11->23->24->12->6->13->14
        A->B->D->H->H左子树->H右子树->I->I左子树->I右子树->E->E左子树->E右子树->C->F->J->J左子树->J右子树->F右子树->G->G左子树->G右子树
        :param root:
        :param pos:
        :return:
        """
        if pos >= len(self.data_list) or self.data_list[pos] == '#':
            # 递归结束条件
            return None
        else:
            root = BiTreeLinkNode(self.data_list[pos])
            # 递归建立左子树
            root.lchild = self.CreateBinaryTree(root, 2*pos+1)
            # 递归建立右子树
            root.rchild = self.CreateBinaryTree(root, 2*pos+2)
            return root

7. 代码框架

class BinaryTree(object):
    def __init__(self, data_list):
        self.data_list = data_list

    def CreateBinaryTree(self, root=None, pos=0):

    def VisitBinaryTreeNode(self, RootNode):
    
    def PreOrder(self, RootNode):

    def PreOrderNoRecursive(self, RootNode):

    def InOrder(self, RootNode):

    def InOrderNoRecursive(self, RootNode):

    def PostOrder(self, RootNode):

    def PostOrderNoRecursive(self, RootNode):

    def LeverOrder(self, RootNode):

if __name__ == '__main__':
    tree_obj = BinaryTree('ABCDEFGHI##J###')
    bitree = tree_obj.CreateBinaryTree()
    print('先序遍历递归: ', end='')
    tree_obj.PreOrder(bitree)
    print('\n先序遍历非递归: ', end='')
    tree_obj.PreOrderNoRecursive(bitree)
    print('\n中序遍历递归: ', end='')
    tree_obj.InOrder(bitree)
    print('\n先序遍历非递归: ', end='')
    tree_obj.InOrderNoRecursive(bitree)
    print('\n后序遍历递归: ', end='')
    tree_obj.PostOrder(bitree)
    print('\n后序遍历非递归: ', end='')
    tree_obj.PostOrderNoRecursive(bitree)
    print('\n层序遍历: ', end='')
    tree_obj.LeverOrder(bitree)

  运行结果如下:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42730750/article/details/108270894