堆 Heap
heap 是一个抽象的数据结构,或者说是逻辑上的数据结构,并不是一个物理上真实存在的数据结构。
heap 其实有很多种实现方式,比如 binomial heap, Fibonacci heap 等等。
但是面试最常考的,也是最经典的,就是二叉堆,也就是用一棵完全二叉树来实现的。
Java中的优先队列PriorityQueue的实现就是使用的 堆。
特点
- 完全二叉树
- 任意节点都优于它的所有孩子
- 一般使用数组来实现
已知父节点下标计算左右孩子节点
1. 从 1 下标开始
leftChild = 2Pa
rightChild = 2Pa
Pa = Child / 2
2. 从 0 下标开始
leftChild =2*(Pa+1) - 1 = 2Pa+1
rightChild =2(Pa+1) + 1 - 1 = 2*Pa+2
Pa = (Child + 1)/2 -1 =( Child - 1) / 2从 0 下标开始的节点下标计算可以 从 1 下标开始计算推理而出,先转换为下标 1 的表示方式(+1),通过计算转换为下标1开始的父节点或孩子节点后再(-1)转换为 0 下标的方式表示
基本操作
- 创建(从最后一个节点的父节点开始,b)
- 增加(a)
- 删除(b)
核心:
a. 向上调整
b. 向下调整
以下都以大顶堆为例
堆的创建
- 选择初始开始结点下标 initIndex(最后一个节点的父节点)
- 以该位置开始进行更新(向下调整)
- initIndex–
- 继续更新,直到完成栈顶
从1开始 :initIndex = size/2
从0开始 :initIndex = (size - 1) / 2
增加结点
- 插入到最后的位置
- 更新堆 (向上调整)
删除结点
分为 2 中类型,删除堆顶和任意位置删除
去除堆顶
- 使用最后的结点替换堆顶
- 更新堆 (向下调整)
去除任意结点
一般不进行这个操作,但可以实现待添加
向上调整
只需要当前结点和其父亲结点进行比较,arr[current] > arr[Pa] 则交换并继续向上,反之结束
向下调整
需要和两个孩子结点相比较,这儿我们先比较两个孩子节点,arr[current] < arr[Child] 则交换,继续向上,反之结束。
示例代码
package com.company;
import sun.plugin.javascript.navig.Link;
import java.util.*;
public class Main {
public static void main(String[] args)
{
int[] arr = {0,1,2,3,4,5,6,7};
System.out.println(Arrays.toString(arr));
Heap heap = new Heap(arr);
System.out.println(Arrays.toString(heap.arr));
heap.offer(10);
System.out.println(Arrays.toString(heap.arr));
heap.pop();
System.out.println(Arrays.toString(heap.arr));
}
}
// 大顶堆
class Heap{
int[] arr; // 存放堆
int size; // 堆大小
public Heap(){};
public Heap(int[] tarr){
this.size = tarr.length;
this.arr = Arrays.copyOf(tarr,tarr.length + 10); // 初始化 多加10个空间
int lastIndex = tarr.length-1; // 最末尾下标 是 长度减一
for(int i= (lastIndex-1)/2; i>= 0;i--) // 从 (lastIndex-1)/2 开始向下调整
adjustDown(i);
};
public void adjustUp(int loc) // 从下往上调整
{// 只需要和父节点相比较
for(int pa=(loc-1)/2; pa>=0;pa = (pa-1)/2)
{
if(isBig(loc, pa)){
swap(loc, pa);
loc = pa; // 依次向上
}
if(pa == 0)
return;
}
}
public void adjustDown(int loc) // 从上往下调整
{// 需要和两个孩子结点相比较,这儿我们先比较两个孩子节点
for(int ch=2*loc+1; ch < size; ch=2*loc+1)
{
if(ch+1 < size && isBig(ch+1, ch))
ch++;
if(isBig( loc , ch)) break;
else{
swap(loc,ch);
loc = ch; // 依次向下
}
}
}
public void offer(int data) // 加入数据,首先加入到末尾
{
int loc = size;
arr[size++] = data;
if(size == 1)
return;
adjustUp(loc);
}
public int pop() // 弹出堆顶
{
int ret = arr[0];
arr[0] = arr[size-1];
size--;
adjustDown(0);
return ret;
}
public boolean isBig(int a, int b) // 比较
{// 现在是大顶堆,arr[a] > arr[b] 换位 < 符号就是小顶堆
return arr[a] > arr[b] ? true:false;
}
public void swap(int ia,int ib)
{// 辅助交换(注:其实可以不用交换,使用一个临时空间的方式)
// 但是使用 交换更好理解代码
int tmp = arr[ia];
arr[ia] = arr[ib];
arr[ib] = tmp;
}
}