赞
踩
小题考频:30
大题考频:8
难度:☆☆☆
二叉树是
n
(
n
≥
0
)
n(n≥0)
n(n≥0)个结点的有限集合:
①或者为空二叉树,即n = 0。
②或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
二叉树也是递归定义的数据结构
特点:
①每个结点至多只有两棵子树
②左右子树不能颠倒(二叉树是有序树)
注意区别:度为2的有序树
度为2的有序树是指,在这个树里面至少会有一个结点,其有两个子树。
特点:
①只有最后一层有叶子结点
②不存在度为1的结点
③按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父节点为[i/2] (如果有的话)【规律】
除了最下面一层叶子结点之外,所有的分支结点都长满了两个分支。
对于高度为h的满二叉树,第一层 2 0 2^0 20个,第二层 2 1 2^1 21个,第三层 2 2 2^2 22个……第h层有 2 h − 1 2^{h-1} 2h−1个,对其求和可以算出高度为h的满二叉树有多少个结点。
对于有h层的满二叉树,每一层的节点数是上一层节点数的2倍。因此,第i层的节点数为2^(i-1)。将所有层的节点数相加即可得到该满二叉树的总节点数。
∑ i = 0 h − 1 2 i = 2 h − 1 \sum_{i=0}^{h-1}2^{i} = 2^{h} - 1 i=0∑h−12i=2h−1
因此,有h层的满二叉树共有 2 h − 1 2^h-1 2h−1个节点。
完全二叉树:如果一棵树里的结点同样按照一层一层的方式编号,这些编号能够和满二叉树
一一对应,那么这棵树就是完全二叉树:
可以这么理解,满二叉树每一层的结点都已经满了,不可能再有更多的结点。那么完全二叉树就是在满二叉树的基础上,将其某一些编号更大的结点给去掉。
特点:
特点:
①只有最后两层可能有叶子结点
②最多只有一个度为1的结点
③按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父节点为[i/2] (如果有的话)【规律同上】
④i ≤ [n/2] 为分支结点,i > [n/2] 为叶子结点【可取整】
如果去掉了中间的某个结点,那么编号就无法与满二叉树对应。
只有左边结果之后,才能继续往右边结。
完全二叉树中,如果某结点只有一个孩子,那么一定是左孩子。
二叉排序树:一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
左子树上所有结点的关键字均小于根结点的关键字;
右子树上所有结点的关键字均大于根结点的关键字。
左子树和右子树又各是一棵二叉排序树。
左子树都小于根节点;
右子树都大于根节点
单独看左子树和右子树,其又是一颗二叉排序树。
基于这种特性,想要在二叉排序树上搜索某一个关键字就会变得很容易。
Eg1.如果想要找到60结点
60大于根节点的关键字19,往右子树找;
右子树根节点是50,小于60,继续往右子树找;
右子树根节点为66,大于60,往左子树找;
找到了关键字为60的结点,至此就完成了一个关键字的搜索。
Eg2.插入新结点68
68大于19,往右;
68大于50,往右;
68大于66,往右;
68小于70,同时已经到叶子结点,让结点68成为结点70的左孩子。
这样就保证了在插入一个新元素之后,这个树依然是二叉排序树。
二叉排序树可用于元素的排序、搜索
平衡二叉树:树上任一结点的左子树和右子树的深度(高度)
之差不超过1。
胖胖的、丰满的树
50的左子树的深度(高度)为2,右子树的深度(高度)为3,满足平衡二叉树的条件。
66的左子树的深度(高度)为1,右子树的深度(高度)为2,满足平衡二叉树的条件。
所有结点都满足。
再看一个例子:
根节点的左子树深度(高度)为1而右子树深度(高度)为6,其深度(高度)之差已经大于1,不是平衡二叉树。
尽可能追求左右子树的平衡,就可以得到更好的更高效的二叉排序树。
给出的这两个例子其实都是二叉排序树,而且里面存的数据元素都是一样的。
如果要搜索关键字为70的结点,在平衡二叉树上往下走2步就可以找到;
在不平衡二叉树上要走很多很多步才能找到。
平衡二叉树能有更高的搜索效率
长得越胖越好,高度尽可能低,从根节点开始往下搜索时,因为高度低,所以搜索的次数就会减少。
常见考点1:设非空二叉树中度为0、1和2的结点个数分别为 n 0 n_0 n0、 n 1 n_1 n1和 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1(叶子结点比二分支结点多一个)
度为0的点(叶子结点)比度为2的点要多一个。
假设树中结点总数为n,因为二叉树里只可能有度为0,度为1和度为2这样三种结点,所以三种结点的数量加起来刚好是n
① n = n 0 + n 1 + n 2 n = n_0 + n_1 + n_2 n=n0+n1+n2
树的结点个数等于总度数+1。
因为对任何一种树来说,除了根节点之外,每一个结点的头上都会连一个分支,总度数就是这些分支的总数量,只有根节点头上是没有分支的,所以得出这样一个结论。
对于二叉树来说,度为1的结点有 n 1 n_1 n1个,度为2的结点有 n 2 n_2 n2个——其每个结点贡献2个度, n 1 + 2 n 2 n_1+2n_2 n1+2n2得到的就是总度数,总度数+1刚好是树的结点个数。
② n = n 1 + 2 n 2 + 1 n=n_1+2n_2+1 n=n1+2n2+1
此处用②式-①式就可以得到这个结论:
n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1
十分重要
二叉树第i层至多有
2
i
−
1
2^{i-1}
2i−1个结点(i≥1)
m叉树第i层至多有
m
i
−
1
m^{i-1}
mi−1个结点(i≥1)
高度为h的二叉树至多有2ℎ − 1个结点(满二叉树)
高度为h的m叉树至多有
m
h
−
1
m
−
1
\frac{m^h-1}{m-1}
m−1mh−1个结点
(等比数列求和)
具有n个(n > 0)结点的完全二叉树的高度h为 ⌈ log 2 ( n + 1 ) ⌉ \lceil \log _2\left( n+1 \right) \rceil ⌈log2(n+1)⌉ 或 ⌈ log 2 n ⌉ + 1 \lceil \log _2n \rceil +1 ⌈log2n⌉+1
n的上下限,取对数
高为h的完全二叉树至少 2 h − 1 − 1 2^{h-1} − 1 2h−1−1 个结点,至多 2 h − 1 2^ℎ − 1 2h−1个结点
高为h的完全二叉树至少可以比高为h-1的满二叉树多1个结点
可以得到:第i个结点所在层次为 ⌈ log 2 ( i + 1 ) ⌉ \lceil \log _2\left( i+1 \right) \rceil ⌈log2(i+1)⌉或 ⌈ log 2 i ⌉ + 1 \lceil \log _2i \rceil +1 ⌈log2i⌉+1
完全二叉树只可能有一个度为一的结点
n 0 n_0 n0为叶子结点, n 2 n_2 n2为两分支结点,所以 n 0 + n 2 n_0+n_2 n0+n2 = 2 n 2 + 1 2n_2+1 2n2+1一定为奇数。
可以得到:
如果一个二叉树有偶数个结点的话,这就意味着 n 0 + n 1 + n 2 n_0+n_1+n_2 n0+n1+n2是一个偶数 2 k 2k 2k,由于 n 0 + n 2 n_0+n_2 n0+n2一定是一个奇数,那么 n 1 n_1 n1的值就是1,得到 n 0 = k n_0 = k n0=k、 n 2 = k − 1 n_2=k-1 n2=k−1
如果一个二叉树有奇数个结点的话,这就意味着 n 0 + n 1 + n 2 n_0+n_1+n_2 n0+n1+n2是一个奇数 2 k − 1 2k-1 2k−1,由于 n 0 + n 2 n_0+n_2 n0+n2一定是一个奇数,那么 n 1 n_1 n1的值就是0,得到 n 0 = k n_0 = k n0=k、 n 2 = k − 1 n_2=k-1 n2=k−1
一个 TreeNode就对应树中一个结点
每一个结点里包含一个ElemType,即实际要存的数据元素。
通过bool型变量来判断是否为空结点。
初始化数组时,需要把所有元素的isEmpty都设置为true,表示所有数据都是空的,没有存数据。
让t[0]位置空缺,因为完全二叉树的结点编号是从1开始的,从t[1]开始存储,那么数组的下标就刚好反映出完全二叉树里的结点编号。
由于数组是静态数组,长度是有限的,那么完全二叉树可以包含的结点数量也是有限的。
如果不是完全二叉树,也按照层序将各结点顺序存储:
但是我们,把二叉树的结点编号与完全二叉树对应起来,那么也可以反映出逻辑关系。
但无法判断结点的左右孩子,只能通过isEmpty来判断。
如果isEmpty为true,则说明该位置为空结点。
使用顺序存储来存储二叉树,里面会有大量空间是闲置的,这样会浪费大量空间。
最坏情况:所有的结点都只有右分支;
那么对于这个高度为h的二叉树,也至少需要 2 h − 1 2^h-1 2h−1个存储单元。即高度为h的满二叉树的存储空间。
结论:二叉树的顺序存储结构,只适合存储完全二叉树。
二叉链表:
每一个结点都有一个data域用来存放实际的数据元素;
然后还有两个指针,分别指向其左孩子和右孩子;
如果一个结点没有孩子,就把对应的指针设为NULL
由于每个结点都有两个指针域,那么n个结点就有2n个指针域;
除了根节点,其他n-1个结点都连有一个指针,那么其他 n + 1 n+1 n+1个指针指向空,可以利用起来构造线索二叉树。
定义结构体:
定义一颗空树,声明一个指向根节点的指针,初始值为NULL:
此时是一个空树
插入根结点,用malloc函数申请一个根结点:
此时这里在根结点中存入数字1;
并且让根结点的左右指针此时指向NULL
插入新结点,用malloc函数申请一个新结点:
在该结点中存入2;
把根结点的左孩子指针指向当前结点p
那么利用相同方法可以插入其他结点
只需要检查该结点的左孩子指针和右孩子指针指向哪里即可。
如果数据很多时,通过遍历的方法找某一结点的父结点就会很耗时。
如果在应用场景中,经常需要逆向查找某一结点的父节点
那么可以在该结构体中再定义一个指针,指向其父结点。
这样的实现方式称为:三叉链表——方便找父结点
下一节中,将介绍如何遍历
二叉树的各个结点。
顺序存储二叉树,一定要把二叉树的结点编号和完全二叉树一一对应起来。
——这样才能通过结点编号来确定各个结点之间的关系。
上文中的探讨,结点都是从1开始。
——思考:如果结点从0开始编号,如何找到结点i的左孩子、右孩子和父结点。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。