1 二叉树的顺序存储: 使用数组保存二叉树结构,即将二叉树 按照层序遍历 的方式放入数组.这种方式一般只适用于完全二叉树,一般的二叉树会造成空间浪费比较严重.
2 堆(heap) 在逻辑上就是一个完全二叉树,在物理上保存在数组中.
(1) 满足任意结点的值都大于其子树的结点的值.叫做大堆(最大堆), 反之是小堆(最小堆).
(2) 堆的作用: 快速找到集合中的最值.
3 堆中元素下标关系: 已知双亲(parent)的下标, 或已知左右孩子(child)下标时:
(1) 当根节点下标从0开始计算时, 左孩子(left)下标 = 2 × parent + 1, 右孩子(right)下标 = 2 × parent + 2. 双亲下标(不区分左右子树) = (child-1) / 2;
(2) 当根节点下标从1开始计算时, 左孩子(left)下标 = 2 × parent , 右孩子(right)下标 = 2 × parent + 1. 双亲下标(不区分左右子树) = child / 2;
4 向下调整:(注意) 前提是左右子树必须已经是一个堆,才能进行调整.
(1) 通过size指定 array 中哪些元素是有效的堆元素, 即有效元素个数.
(2) index 表示从哪个位置的下标开始调整.
(3) 这里以小堆为例,根节点下标为0的调整.
public static void shiftDown(int[] array, int size, int index) {
int parent = index;
int child = 2 * parent + 1; // 根据 parent 下标找到左子树的下标
while (child < size) {
// 比较左右子树, 找到较小值
if (child + 1 < size && array[child + 1] < array[child]) {
child = child + 1;
}
if (array[child] < array[parent]) {
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
} else {
break;
}
// 更新 parent 和 child, 处理下一层的数据.
parent = child;
child = 2 * parent + 1;
}
}
大堆的 向下调整过程:
public static void shiftDown(int[] array, int size, int index) {
int parent = index;
int child = 2 * parent + 1;
while (child < size) {
if (child + 1 < size && array[child + 1] > array[child]) {
child = child + 1;
}
if (array[child] > array[parent]) {
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
} else {
break;
}
parent = child;
child = 2 * parent + 1;
}
}
4 建堆:把一个无序数组建成一个堆, 可以用向下调整也可以向上调整. 一般是向下调整.
建堆过程, 找到最后一个非叶子结点开始进行向下调整,需要从后往前遍历数组. 时间复杂度为(OlogN)
以向下调整构建小堆为例:
//建堆:找到最后一个非叶子节点,然后进行向下调整操作,每次之后i--
public static void createHeap(int[] array, int size) {
for (int i = (size - 1 - 1) / 2; i >= 0; i--) {
shiftDown(array, size, i);
}
}
public static void main(String[] args) {
int[] array = {9,5,2,7,3,6,8};
createHeap(array, array.length);
System.out.println(Arrays.toString(array));
}
//向下调整建好的小堆: [2, 3, 6, 7, 5, 9, 8]
5 如果是向上调整的话,就找根节点紧挨的第一个双亲节点作为起始位置, 从前往后进行遍历数组.
6 构建堆的应用: 优先级队列(PriorityQueue)(注意这是一个队列,都是队列有关的性质):通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理优先级次高的对象.
操作-----入队列(以大堆为例):
(1) 首先按尾插方式放入数组.
(2) 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
(3) 否则, 交换其和双亲位置的值,重新进行 2、3 步骤, 直到根结点.
private static void shiftUp(int[] array, int index) {
int child = index;
int parent = (child - 1) / 2;
while (child > 0) {
if (array[parent] < array[child]) {
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
} else {
// 发现当前父节点比子节点大. 这个时候说明整个数组已经符合堆的结构了
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
操作-----出队列(优先级最高) (以大堆为例):
(1) 下标为 0 的元素就是队首元素. 删掉的同时, 我们也希望剩下的结构仍然是一个堆.
(2) 实际上是用最后一个元素替换队首元素,然后删除最后一个元素,同时再进行向下调整.
public int poll() {
int oldValue = array[0];
array[0] = array[size - 1];
size--;
shiftDown(array, size, 0);
return oldValue;
}
操作------取队首元素(优先级最高) (以大堆为例):
返回堆顶元素即可.
public int peek() {
return array[0];
}
7 TopK问题.