一、堆
0、预备知识
①使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。这种方式的主要用法就是堆的表示。
②在数组中:左孩子(left)下标 = 2 * parent + 1;
右孩子(right)下标 = 2 * parent + 2;
双亲(parent)下标 = (child - 1) / 2;
1、堆的概念
① 堆在逻辑上是一个完全二叉树。
②堆在物理实现上是保存在数组中的。
③大堆和小堆:满足任意结点的值都大于其子树中结点的值,叫做大堆,反之叫小堆。
④ ※堆的基本作用是,快速找集合中的最值
2、堆的操作——向下调整
前提: 要调整的结点左右子树必须已经是一个堆。
说明:
1.array 代表存储堆的数组
2.size 代表数组中被视为堆数据的个数
3.index 代表要调整位置的下标
4.left 代表 index 左孩子下标
5.right 代表 index 右孩子下标
6. min 代表 index 的最小值孩子的下标
7. 向下调整一次的时间复杂度为log2n
public static void shiftDown(int[] array,int size,int index){
int left = 2 * index + 1;
while (left < size){ //判断 index 位置有没有孩子因为堆的存储结构是数组,
// 所以判断是否有左孩子即判断左孩子下标是否越界,
int min = left;
int right = 2 * index + 2;
if(right < size){
if(array[right] < array[left]){//确定 left 或 right,谁是 index 的最
// 小孩子 min
min = right;
}
}
if (array[index] <= array[min]){ //比较 array[index] 的值 和
// array[min] 的值
break;
}
int t = array[index]; // 否则,交换 array[index] 和 array[min] 的值
array[index] = array[min];
array[min] = t;
index = min; //然后因为 min 位置的堆的性质可能被破坏,
// 所以把 min 视作 index,向下重复以上过程
left = 2 * index + 1;
}
}
3、堆的操作——建堆
使用向下调整把一个不是堆的数组变成堆。
时间复杂度为nlog2n
思路:从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
public static void createHeap(int[] array, int size) {
for (int i = (size - 1) / 2; i >= 0; i--) {
shiftDown(array, size, i);
}
}
4、堆的应用——优先级队列
是一种不同于先进先出队列的队列。每次从队列中取出的是有最高优先权的元素,有两个操作 入队列 和 出队列(出优先级最高的) 常使用堆来构建。
① 入队(大堆):
过程: 以大堆为例
1. 首先按尾插方式放入数组
2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质, 插入结束
3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤
4. 直到根结点
public static void shiftUp(int[] array, int index) {
while (index > 0) { //3.直到根结点
int parent = (index - 1) / 2;
if (array[parent] >= array[index]) {
break; //1.比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质, 插入结束
}
int t = array[parent];//2.否则,交换其和双亲位置的值,重新进行 2、3 步骤
array[parent] = array[index];
array[index] = t;
index = parent;
}
}
②出队(优先级最高的)
过程: 为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆。
5、TopK问题
①问题: 从大量的数据中找出K个最大(最小)的值。
②思路: 将待找的元素序列的前K个元素建立一个小堆,每次和堆顶元素进行比较。如果数组当中的当前元素,大于堆顶元素,进行替换,并向下调整。先出堆顶元素,然后进行入堆。