1. 树
我们将 现实生活中的树,倒过来看, 可以构成树; 树上的每个元素可以看成节点;
节点与节点之间的关系, 上下节点间 作为 父子节点, 左右节点之间作为兄弟节点;
- 根节点: 将没有父节点的节点称为 根节点;
- 叶子节点: 将没有子节点的 节点称为 叶子节点;
树中 比较相似 却又不同的三个 概念: 高度height, 深度depth , 层level;
打个比方, 记住高度和深度的区别:
高度: 相当于数楼房时, 从下往上开始数的;
深度: 相当于看井, 从上往下看的;
高度:从叶子节点开始往上看, 到该节点的距离;
深度:从根节点开始往下走, 到该节点的距离;
------------- 但是, 这里的距离却有着两种定义的方式 ----------------------
- 这两者的区别,就好比你往数组里 存数据时,
- 你是从下标0 开始存的, 还是从下标 1 开始存的;
- 无论从下标0 ,还是从下标1 开始存, 最终存入的个数都是相同的;
- 最终存入的个数相同 = 也即 两种定义的方式其层数始终都是相同的;
1.1 距离 = 边数 : 从 0 开始版
- 节点的高度 = 叶子节点到该节点的最长路径长度(边数)
- 节点的深度 = 根节点 到这个节点的路径长度(边数)
涉及到树时, 该节点便是 根节点; 这个节点便是叶子节点;
-
树的高度 = 最远叶子节点到根节点的 边数
-
树的深度 = 根节点 到叶子节点的 边数
-
节点的层 = 节点的深度 + 1
这种定义的方式:
- 叶子节点的高度 是从0 开始;
- 根节点的深度 从 0 开始;
- 深度 和 层数之间 相差数值 1;
1.2 距离 = 节点数 : 从 1 开始版 ( leetcode 版)
- 节点的高度 = 叶子节点到该节点的节点个数(包括自身)
- 节点的深度 = 根节点 到这个节点的节点数(包括自身)
涉及到树时, 该节点便是根节点; 这个节点便是叶子节点;
-
树的高度 = 叶子节点到根节点的 节点数
-
树的深度 = 根节点 到叶子节点的 节点数
-
节点的层 = 节点的深度
这种定义的方式:
- 叶子节点的高度 是从1 开始;
- 根节点的深度 从 1 开始;
- 深度 和 层数 两者 等价 相同;
为了 学以致用, 我们采用 第二种定义方式;
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的结点在同一层上,则这棵二叉树为满二叉树。
满二叉树的条件:
- 所有的叶子节点 都在最底层;
- 除了叶子节点外, 所有节点都有左右子节点;
2.2.2 满二叉树的性质
满二叉树除了满足普通二叉树的性质,还具有以下性质:
- 满二叉树中第 i 层的节点数为 2n-1 个。
- 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
- 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
- 具有 n 个节点的满二叉树的深度为 log2(n+1)。
2.3 完全二叉树
2.3.1 完全二叉树的性质
完全二叉树的满足条件:
- 叶子节点只分布在最底层和倒数第二层;
- 最底层的叶子节点,都会靠左排列;
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 代表空节点;按照 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