目录
1.2物理层面上的数组 是如何 和逻辑上的完全二叉树关联起来的
堆:
1.1概念
堆:一种有特殊用途的数据据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中找到最值(最大值或者最小值)。
堆在物理层面上表现为一组连续的数组区间;堆在逻辑上表现为一棵完全二叉树
1.2物理层面上的数组 是如何 和逻辑上的完全二叉树关联起来的
long[] array = {1, 2, 3, 4, 5, 6, 7, 8 ,9 } size = 6 把数组中的前六个看成完全二叉数。
按照层序方式排列出数组中的元素
完全二叉树中的下标遵循以下规则:双亲的下标记为 parent;左孩子的下标记为 left;右孩子的下标记为 right;忽略左右孩子的下标记为 child。如果我们已知某个 parent 的下标,则:
left = 2 * parent + 1; right = 2 * parent + 2;
如果已知 child 的下标,无论左右,则:parent = (child - 1)/ 2。根的下标一定是0,完全二叉树中的最后一个(以层序为顺序)结点的下标:size - 1。
数组 -> 完全二叉树:按照层序排列
完全二叉树 -> 数组:处理下标关系
1.3 大小堆
1.4堆的核心操作——向下调整操作(justDown)
1.4.1可以向下调整操作的前提:
调整是针对某个位置(某个结点、某个下标)。除了这个结点以及它的两个孩子外,要求完全二叉树的其余部分已经满足堆的性质。
1.4.2向下调整的具体操作——只用完全二叉树的视角表示
long[] array = {2, 5, 1, 7, 13, 3, 5 ,9, 8} size = 9
例:要调整的是 2 元素这个位置,也就是[0] 所在位置 (调整成小堆)
①明确要调整的结点,是不是叶子结点:是叶子,操作结束(出口一);不是叶子,进行下一步
②比较要调整结点的左右两个孩子的值,找到其最值(小堆就是找最小值)
根据 2 所在的下标,找到左孩子的下标和右孩子的下标 left = 2 * parent + 1;right = 2 * parent + 2
注意:2 所在的结点,一定不是叶子。由于我们这棵树是完全二叉树,不可能出现只有右孩子没有左孩子的结点。2 所在的结点可能有两种情况 只有左孩子 或者 左右孩子都有。
③让左右孩子的最值 和 要调整的结点的值进行比较。 结点的值 <= 孩子的最值;结点的值 > 孩子的最值
④交换结点的值 和 孩子的最值
⑤虽然 1,5,2之间符合堆的性质了,但把 2 交换下来了,所以 2,3 ,5之间可能破坏堆的性质,所以再对 [2] 下标处再次进行判断操作,回到①这一步进行再次的循环,如果没有破坏堆的性质,则结束操作(出口二)。
1.4.3向下调整方法签名:
1.名称:shiftDown / adjustDown / heapify
2.方法的返回值类型:没有调整失败的情况,也不需要告诉调用者任何结论的内容,使用 void
3.参数列表:
1.要调整的堆(满足可调整的前提)堆逻辑上是完全二叉树,但物理上是一个数组。所以在代码中直接表现为一个数组即可(带上size),如果不带size ,则视为把整个数组看成堆。long[] array,int size
2.要调整的位置——表现为某个结点——物理上是数组——要调整位置的下标 int index
4.public
5.是不是属于某个对象? 不属于,所以可以使用static
public class HeapTest {
/**
* 小堆的向下调整,要求满足向下调整的前提
* @param array 堆所在的数组
* @param size 前 size 个元素视为堆中的元素
* @param index 要调整位置的下标
*/
public static void shiftDown(long[] array, int size, int index) {
while (true) {
//1.判断 index 所在位置是不是叶子
// 没有左孩子一定就是叶子了
int left = index * 2 + 1;
if(left >= size){
//越界 没有左孩子 是叶子 调整结束
return;// 循环的出口一:走到的叶子的位置
}
// 2. 找到两个孩子中的最小值
// 先判断有没有右孩子
int right = left + 1;
int min = left;
if(right <size && array[right] < array[left]){
// 有右孩子为前提的情况下,然后右孩子的值 < 左孩子的值
// right < size 必须在 array[right] < array[left] 之前,不能交换顺序
// 因为先得确定有右孩子,才有比较左右孩子的意义
min = right;
}
// 3. 将最值和当前要调整的位置进行比较,判断是否满足堆的性质
if(array[index] <= array[min]){
// 当前要调整的结点的值 <= 最小的孩子值;说明这里也满足堆的性质了
//所以,调整结束
return; // 循环的出口二:循环期间,已经满足堆的性质了
}
// 4. 交换两个值
long temp = array[index];
array[index] = array[min];
array[min] = temp;
// 5. 再对 min 位置重新进行同样的操作(对 min 位置进行向下调整操作)
index = min;
}
}
public static void main(String[] args) {
long[] array = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
shiftDown(array, array.length, 0);
}
}
1.5建堆操作:
帮助我们将一个完全无规则的数组(完全二叉树)构建成堆
/**
* 创建小堆:从一个无规则数组开始,经过调整,得到一个小堆
* @param array 存储堆元素的数组
* @param size 前size 元素视为堆中元素
*/
public static void createHeap(long[] array,int size){
// 从最后一个非叶子结点的双亲开始
// 最后一个结点的下标一定是:size - 1
// 他的双亲一定是: (size - 2) / 2
// 从后往前遍历,直到根也被向下调整
for(int i = (size - 2) / 2 ;i >= 0;i--){
shiftDown(array,size,i);
}
}
现实中,我们一般把这组需要不断找到最值、变动频繁的数据集称为优先级队列——背后其实就是堆
1.6取出优先级队列每次取出集合中最小值
Collection(I) -> Queue(I) - > PriorityQueue(C)
offer(...) 向队列中放入元素 poll(...) 取出队列中的元素(取出来的是优先级队列中最小的元素) peek(...) 查看最小元素,不删除
前提:优先级队列中的元素类型要求具备比较能力:元素类型本身是 Comparable;通过构造方法传入一个Compara。
public class Dome1 {
public static void main(String[] args) {
// 使用 Long 类型作为优先级队列的元素类型
// 由于 Long 类型本身具备比较能力(Long 本身实现了 Comparable)
PriorityQueue<Long> pq = new PriorityQueue<>();
// 乱序的放入元素
pq.offer(3L);
pq.offer(5L);
pq.offer(1L);
pq.offer(4L);
// 查看队首元素 —— 最小的元素
Long peek = pq.peek();
System.out.println(peek);
System.out.println("==========");
while(!pq.isEmpty()){
Long poll =pq.poll();
System.out.println(poll);
}
}
}
1.7取出优先级队列每次取出集合中的最大值
public class Dome2 {
public static void mian(String[] args){
// 如果我们想让优先级队列每次取出集合中的最大值,该如何操作?
// 元素类型虽然支持比较能力,但其比较能力不是我们需要的方式,同样通过 Comparator 解决
PriorityQueue<Long> pq = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));
// 乱序的放入元素
pq.offer(3L);
pq.offer(5L);
pq.offer(1L);
pq.offer(4L);
// 查看队首元素 —— 最大的元素
Long peek = pq.peek();
System.out.println(peek);
System.out.println("==========");
while(!pq.isEmpty()){
Long poll =pq.poll();
System.out.println(poll);
}
}
}
对于pq 队列的写法:
PriorityQueue<Long> pq = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));
也可以写成
PriorityQueue<Long> pq = new PriorityQueue<>(new Comparator<Long>() {
@Override
public int compare(Long o1, Long o2) {
return -o1.compareTo(o2); // 按照 Long 的原有比较逻辑去比较
}
});
PriorityQueue<Long> pq = new PriorityQueue<>((Long o1, Long o2) ->
return -o1.compareTo(o2);
});
PriorityQueue<Long> pq = new PriorityQueue<>((Long o1, Long o2) -> -o1.compareTo(o2));