树的基本概念
- 节点的度: 这个节点子树的个数
- 树的度: 所有节点中度的最大值
- 叶子节点: 度为0的节点
- 非叶子节点: 度不是0的节点
- 节点的深度: 从根节点到当前节点唯一路径上的节点总数
- 节点的高度: 从当前节点到最远叶子节点的路径上的节点总数
- 树的深度: 所有节点深度的最大值
- 树的高度: 所有节点高度的最大值
- 树的深度等于树的高度
有序树
- 树中任意节点的子节点之间有顺序的关系
无序树
- 树中任意节点的子节点之间没有顺序关系
二叉树
- 每个节点度的最大值为 2
- 一个节点最多拥有2个子树
- 左子树和右子树是有顺序的
- 即使只有一个节点,也要区分左右子树
二叉树的性质
非空二叉树的第 i 层 最多有多少个节点 (i >= 1 )
观察可得规律如下
第一层有 1 个 | 2 的 0次幂 |
---|---|
第二层有 2 个 | 2 的 1次幂 |
第三层有 4 个 | 2 的 2次幂 |
第四层有 8 个 | 2 的 3次幂 |
第 i 层最多有 2 的 i -1 次幂个节点
在高度为 h 的二叉树上最多有多少个节点
观察可得规律如下
高度为 1 | 2 的 1次幂 - 1 | 1 |
---|---|---|
高度为 2 | 2 的 2次幂 - 1 | 3 |
高度为 3 | 2 的 3次幂 - 1 | 7 |
高度为 4 | 2 的 4次幂 - 1 | 15 |
举例:
高度为3的二叉树包含的总节点数包括: 第一层、第二层、第三层节点数量的总和
第一层
2
0
=
1
2^0 = 1
20=1
第二层
2
1
=
2
2^1 = 2
21=2
第三层
2
2
=
4
2^2=4
22=4
高度为 3 的 二叉树最多的节点数量为
2
0
+
2
1
+
2
2
=
7
2^0 + 2^1 + 2^2 = 7
20+21+22=7
2 3 − 1 = 7 2^3 -1 = 7 23−1=7
高度为 h 的二叉树上最多有 2 的 h 次幂 - 1 个节点
一个非空二叉树,叶子节点和度为2节点之间数量的关系
度为2的节点 = 叶子节点数量 + 1
- 度为 2 的节点是除了最底层节点之外的所有节点
- 叶子节点是最底层的节点
一个非空二叉树,节点总数与度之间的关系
二叉树节点数 = 度为2 + 度为1 + 度为 0
森林
- m (m>0) 棵互不相交的树组成的集合
满二叉树
完全二叉树
这样的一个二叉树叫做完全二叉树,可以看到节点从上往下、从左往右其节点的编号是和满二叉树一致的,下面是一些基本的性质
- 其叶子节点只会出现在最后的两层
- 除去最后一层的完全二叉树就是一个满二叉树
- 其最后一层的叶子节点全部是靠左对齐
- 满二叉树是一个完全二叉树,完全二叉树不一定是满二叉树
二叉堆
二叉堆其实就是一个完全二叉树,其存在的意义在于快速获得一个序列中的最大值或者最小值。其最大值或者最小值是存储在根节点位置上的。
堆的性质
- 根节点的值 >= 子节点的值 大顶堆
- 根节点的值 <= 子节点的值 小顶堆
如下的这一个二叉堆 根节点位置是 1 也就是一个小顶堆。
由于二叉堆逻辑结构上是一个完全二叉树,节点之间是按照顺序排列的,在存储之上可以采用数组的形式来存储节点。
数组索引的规律 (n 代表着数组的索引)
- 0 位置的元素是根节点
- n > 0 节点的父节点 floor ( (i - 1 ) / 2 )
实现方案
添加元素
添加元素的时候直接添加,但是在添加之后需要将这个完全二叉树调整为一个二叉堆。
如果对上面的小顶堆在添加一个元素 3 ,那么此时这个完全二叉树就成为了下面这个样子。 也就不符合小顶堆的性质要求。所以在添加完毕元素之后我们需要对这个完全二叉树进行调整以便其依旧满足小顶堆的要求。
调整的步骤如下:
- 找到当前要调整元素的父节点
- 如果当前节点比父节点小的话, 就证明当前节点是一个需要调整的节点。
- 此时可以把当前节点和其父节点进行交换。
- 可见此时交换之后 最下面两次符合小顶堆的性质要求, 但是加上根节点就不符合性质要求了。
- 我们还可以按照上面的思路,在此把交换后的节点与其父节点进行大小比较, 然后交换位置。
-
最终经过两次位置交换后的完全二叉树如下
- 此时就符合一个小顶堆的要求了。
整个步骤可以总结为:
- 当前新添加节点与其父节点进行大小比较
- 按照 大 / 小顶堆的要求
- 交换之后重复上面的步骤, 往上追溯
- 直到完全符合 大 / 小顶堆的要求
Code
private void siftUp(int index) {
// 当前位置的元素, 此后需要用它来与它的父节点比较
E element = elements[index];
while (index > 0) {
int pindex = (index - 1) / 2;
int res = compare(element, elements[pindex]);
if (res < 0) {
return;
}
// 开始交换
E temp = element;
elements[index] = elements[pindex];
elements[pindex] = temp;
index = pindex;
}
}
删除元素
小顶堆如下, 现在删除元素,也就是删除了 根节点 。显然删除根结点之后需要对堆进行调整使其满足。
删除的流程如下:
- 让最后一个节点直接替换根节点元素
- 然后开始对根节点元素进行调整
- 让根节点 与 其左右节点进行比较
- 然后按照小顶堆的性质,对根节点和 左 / 右 节点进行交换
- 让根节点 与 其左右节点进行比较
- 一直重复这个过程
- 等到和叶子节点比较之后便可结束
删除元素流程
最后一个元素覆盖根节点元素。
进行节点大小的比较
重复上面操作
Code
/**
* 让index位置的元素下滤
*
* @param index
*/
private void siftDown(int index) {
E element = elements[index];
int half = size >> 1;
// 第一个叶子节点的索引 == 非叶子节点的数量
// index < 第一个叶子节点的索引
// 必须保证index位置是非叶子节点
while (index < half) {
// index的节点有2种情况
// 1.只有左子节点
// 2.同时有左右子节点
// 默认为左子节点跟它进行比较
int childIndex = (index << 1) + 1;
E child = elements[childIndex];
// 右子节点
int rightIndex = childIndex + 1;
// 选出左右子节点最大的那个
if (rightIndex < size && compare(elements[rightIndex], child) > 0) {
child = elements[childIndex = rightIndex];
}
if (compare(element, child) >= 0) {
break;
}
// 将子节点存放到index位置
elements[index] = child;
// 重新设置index
index = childIndex;
}
elements[index] = element;
}