(一)树(高度,深度)与二叉树的实现

1. 树

我们将 现实生活中的树,倒过来看, 可以构成树; 树上的每个元素可以看成节点;
节点与节点之间的关系, 上下节点间 作为 父子节点, 左右节点之间作为兄弟节点;

  • 根节点: 将没有父节点的节点称为 根节点;
  • 叶子节点: 将没有子节点的 节点称为 叶子节点;

树中 比较相似 却又不同的三个 概念: 高度height, 深度depth , 层level;

打个比方, 记住高度和深度的区别:
高度: 相当于数楼房时, 从下往上开始数的;
深度: 相当于看井, 从上往下看的;

高度:从叶子节点开始往上看, 到该节点的距离;
深度:从根节点开始往下走, 到该节点的距离;

------------- 但是, 这里的距离却有着两种定义的方式 ----------------------

  • 这两者的区别,就好比你往数组里 存数据时,
  • 你是从下标0 开始存的, 还是从下标 1 开始存的;
  • 无论从下标0 ,还是从下标1 开始存, 最终存入的个数都是相同的
  • 最终存入的个数相同 = 也即 两种定义的方式其层数始终都是相同的

1.1 距离 = 边数 : 从 0 开始版

  • 节点的高度 = 叶子节点到该节点的最长路径长度(边数)
  • 节点的深度 = 根节点 到这个节点的路径长度(边数)

涉及到树时, 该节点便是 根节点; 这个节点便是叶子节点;

  • 树的高度 = 最远叶子节点到根节点的 边数

  • 树的深度 = 根节点 到叶子节点的 边数

  • 节点的层 = 节点的深度 + 1

这种定义的方式:

  1. 叶子节点的高度 是从0 开始;
  2. 根节点的深度 从 0 开始;
  3. 深度 和 层数之间 相差数值 1;

1.2 距离 = 节点数 : 从 1 开始版 ( leetcode 版)

  • 节点的高度 = 叶子节点到该节点的节点个数(包括自身)
  • 节点的深度 = 根节点 到这个节点的节点数(包括自身)

涉及到树时, 该节点便是根节点; 这个节点便是叶子节点;

  • 树的高度 = 叶子节点到根节点的 节点数

  • 树的深度 = 根节点 到叶子节点的 节点数

  • 节点的层 = 节点的深度

这种定义的方式:

  1. 叶子节点的高度 是从1 开始;
  2. 根节点的深度 从 1 开始;
  3. 深度 和 层数 两者 等价 相同;

为了 学以致用, 我们采用 第二种定义方式;

1.3 节点的度

此外,还有一种定义节点的度;

  • 节点的度 = 该节点孩子的个数
  • 树的 度 = 便是指 所有节点中,节点度的最大值; 即该节点有最多的孩子;

在二叉树, 度最多 为2;

1.4 节点的层次

从一棵树的树根开始,树根所在层为第一层,根的孩子结点所在的层为第二层,依次类推。

对于图 1(A)来说,A 结点在第一层,B、C、D 为第二层,E、F、G、H、I、J 在第三层,K、L、M 在第四层。

1.5 树的表示

常用的表示方法是使用广义表的方式。图 1(A)用广义表表示为:

在这里插入图片描述

(A , ( B ( E ( K , L ) , F ) , C ( G ) , D ( H ( M ) , I , J ) ) )

2. 二叉树

对于二叉树, 便是指 每个节点最多只有两个叉;

2.0 二叉树的定义

简单地理解,满足以下两个条件的树就是二叉树:

  • 本身是有序树;
  • 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;

例如,图 1a) 就是一棵二叉树,而图 1b) 则不是。

在这里插入图片描述

2.1 二叉树的性质

经过前人的总结,二叉树具有以下几个性质:

  • 二叉树中,第 i 层最多有 2i-1 个结点。
  • 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
  • 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。

性质 3 的计算方法为:对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点 n=n0+n1+n2。

同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数 n=B+1。而分枝数是可以通过 n1 和 n2 表示的,即 B=n1+2n2。所以,n 用另外一种方式表示为 n=n1+2n2+1。
两种方式得到的 n 值组成一个方程组,就可以得出 n0=n2+1。

二叉树还可以继续分类,衍生出满二叉树和完全二叉树。

2.2 满二叉树

2.2.1 满二叉树的定义

满二叉树:
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。

或 如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。

满二叉树的条件:

  1. 所有的叶子节点 都在最底层;
  2. 除了叶子节点外, 所有节点都有左右子节点;
    在这里插入图片描述
2.2.2 满二叉树的性质

满二叉树除了满足普通二叉树的性质,还具有以下性质:

  • 满二叉树中第 i 层的节点数为 2n-1 个。
  • 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
  • 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
  • 具有 n 个节点的满二叉树的深度为 log2(n+1)。

2.3 完全二叉树

2.3.1 完全二叉树的性质

完全二叉树的满足条件:

  1. 叶子节点只分布在最底层和倒数第二层;
  2. 最底层的叶子节点,都会靠左排列;

在这里插入图片描述

2.3.2 完全二叉树的性质

完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质,比如说,n 个结点的完全二叉树的深度为 ⌊log2n⌋+1。

⌊log2n⌋ 表示取小于 log2n 的最大整数。例如,⌊log24⌋ = 2,而 ⌊log25⌋ 结果也是 2。

对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立:

  • 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点)

  • 如果 2i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2i 。

  • 如果 2i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2i+1 。

那么为什么完全二叉树,最底层叶子节点都要靠左排列呢?
为什么没有靠右排列呢?

这便是涉及到 完全二叉树的 存储方式 – 数组存储;

3. 二叉树的存储方式

二叉树的 存储方式 可以分为 链式存储 , 和顺序存储;

那么什么样的二叉树 适合用 链式存储, 什么类型的二叉树 又适合 顺序存储呢?

以及为什么适合各自的存储类型呢?

3.1 链式存储

每个节点,包含三个部分:
数据本身, 以及指向 左右子节点的 两个指针;
从根节点开始,通过左右子节点, 将整个树串起来;
链式存储

3.2 顺序存储

如果当前点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2, 其父节点便是 i/2;

而在实践中, 存储时, 下标0空出,用于占位。 下标i 从1 开始,这样左孩子节点 便是 2i, 右孩子节点是 2i + 1;

在这里插入图片描述

3.3 存储类型的选择

4. 树的构建

3.1 根据输入构建二叉树

实践中, 输入如果是一个列表的形式, 来构建二叉树时:

此时 分清是什么顺序的输入列表;

这里给出满足 层序 顺序的输入列表, 来构建二叉树, 并且通过前序遍历打印输出:

  1. 如果 输入列表 是一个层序列表, 此时该列表中 使用 -1 代表空节点;按照 levelInputCreateTree() 构建树
class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

import collections
def levelInputCreateTree(nums: list) -> TreeNode:
    root =  TreeNode(nums[0])  # 将数组首元素 作为根节点;
    que = collections.deque([root])  #  调用 collections 中的 deque, 并且将根节点 输入到其中;

    i, lenNum = 1,  len(nums)  # i 从数组中的下标1开始, 遍历输入到队列中;

    #  迭代法, 创建二叉树
    while i < lenNum:
        cur_Node = que.popleft()  # 弹出双端队列的 左边队首的元素;  该弹出的元素是一个树的节点, 作为当前的节点;

        leftVal, rightVal = nums[i],  nums[i + 1]  if i + 1 < lenNum else -1  #  如果下标 i + 1 超过数组下标, 则赋值为 -1 ;

        if  leftVal != -1 :  #  输入的数组中的 -1 代表此处是 空节点;  或者超出数组下标;
            leftNode = TreeNode(leftVal)  #  将左值 初始化为树的 一个节点;
            cur_Node.left = leftNode  #  将 上述的 左值树节点 赋值为 curNode 的左子树;
            que.append(leftNode)      # 将该节点入队, 便于后序出队, 为他添加子树,也即子节点;

        if  rightVal != -1:
            rightNode = TreeNode(rightVal)
            cur_Node.right = rightNode
            que.append(rightNode)

        i += 2
    return root

def preOrderTraversal(root):  # 测试二叉树建树结果(以输出先序遍历序列为例)
    if not root:
        return
    print(root.val, end=" ")
    preOrder(root.left)
    preOrder(root.right)


if __name__ == "__main__":
    print("--- please input a  level order list to create A Binary Tree---")
    # strip 去除输入中最左边右边的 空格;  split 将输入按空格分割, 返回一个列表;
    #  map 是将input() 输入的字符串 转换为 int 整型;
    nums = list(map(int, input().strip().split()))
    print("you  level order Tree input list : ", nums)
    # 将输入的 层序 列表 建成树;
    tree1 = levelInputCreateTree(nums)

    # 按照 先序遍历树;
    print(" preorder Traversal the Tree: \n", preOrderTraversal(tree1))

# 18 7 11 3 4 5 6



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值