一、什么是堆
先了解两种特别的二叉树
满二叉树
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树
完全二叉树
完全二叉树相对于满二叉树来说,最后一层叶子节点从左到右中间没有空缺的,像这样:
计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树实现。堆的特性如下
- 在大顶堆中,任意节点 C 与它的父节点 P 符合 p.value >= c.value
- 而小顶堆中,任意节点 C 与它的父节点 P 符合 p.value <= c.value
- 最顶层的节点(没有父亲)称之为 root 根节点
完全二叉树可以使用数组来表示,像下图这样
如果从索引 0 开始存储节点数据,它的特征如下:
- 节点i的父节点为 floor((i-1)/2)
- 当 i>0 时,节点i的左子节点为2i+1,右子节点为2i+2,当然它们得小于size
二、堆插入元素
以小堆为例,在插入元素的时候,先把元素放到数组的最后,然后与父节点比较,如果比父节点大,就插入成功了,如果比父节点小就跟父节点交换位置,直到它比父节点大或到达跟节点为止。
下面是代码实现
/**
*
* @param e 添加的元素
* @return 是否成功
*/
public boolean offer (Integer e) {
int i = size;
size = i + 1;
if (i == 0) {
queue[i] = e;
} else {
siftUp(i, e);
}
return true;
}
/**
* 1. 获取父节点
* 2. 让元素与父节点比较,如果小于就交换位置,大于就结束
* @param i 被添加元素的索引
* @param e 被添加元素的值
*/
public void siftUp(int i, Integer e) {
queue[i] = e;
while (i > 0) {
int parent = (i - 1) / 2;
if (e > queue[parent]) {
break;
}
swap(i, parent);
i = parent;
}
}
三、出堆实现
- 弹出数组第一个元素返回
- 把数组最后一个元素放到第一位,然后对它元素进行下潜操作,下潜操作分为这3个步骤
- 计算左子结点和右子节点
- 左子节点和右子节点中较小的一个和父节点比较(子节点的index不能大于size)
- 如果有比父节点小的就交换位置,直到到达叶子节点或者左子节点或右子节点没有小于父节点的就结束
代码实现:
/**
* 1.返回第一个元素
* 2.第一个元素与最后一个元素交换
* 3.size--,最后一个元素置为null
* 4.对第一个元素执行下潜操作
*/
public Integer poll() {
if (size == 0) {
throw new RuntimeException("Null");
}
Integer result = queue[0];
swap(0, size - 1);
queue[--size] = null;
siftDown(0);
return result;
}
/**
* 1. 计算左子结点和右子节点
* 2. 左子节点和右子节点中较小的一个和父节点比较(子节点的index不能大于size)
* 3. 如果有比父节点小的就交换位置,直到到达叶子节点或者左子节点或右子节点没有小于父节点的就结束
* @param index 要下潜元素的索引
*/
public void siftDown(int index) {
int parent = index;
int left = index * 2 + 1;
int right = left + 1;
if (left < size && queue[left] < queue[parent]) {
parent = left;
}
if (right < size && queue[right] < queue[parent]) {
parent = right;
}
// 说明找到更小的了
if (parent != index) {
swap(parent, index);
siftDown(parent);
}
}
四、完整代码
public class Heap {
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Integer[] queue;
private int size = 0;
public Heap() {
queue = new Integer[DEFAULT_INITIAL_CAPACITY];
}
public static void main(String[] args) {
Heap heap = new Heap();
heap.offer(1);
heap.offer(5);
heap.offer(2);
heap.offer(4);
heap.offer(3);
heap.poll();
heap.poll();
}
/**
*
* @param e 添加的元素
* @return 是否成功
*/
public boolean offer (Integer e) {
int i = size;
size = i + 1;
if (i == 0) {
queue[i] = e;
} else {
siftUp(i, e);
}
return true;
}
/**
* 1. 获取父节点
* 2. 让元素与父节点比较,如果小于就交换位置,大于就结束
* @param i 被添加元素的索引
* @param e 被添加元素的值
*/
public void siftUp(int i, Integer e) {
queue[i] = e;
while (i > 0) {
int parent = (i - 1) / 2;
if (e > queue[parent]) {
break;
}
swap(i, parent);
i = parent;
}
}
/**
* 1.返回第一个元素
* 2.第一个元素与最后一个元素交换
* 3.size--,最后一个元素置为null
* 4.对第一个元素执行下潜操作
*/
public Integer poll() {
if (size == 0) {
throw new RuntimeException("Null");
}
Integer result = queue[0];
swap(0, size - 1);
queue[--size] = null;
siftDown(0);
return result;
}
public void swap(int i, int j) {
int temp = queue[i];
queue[i] = queue[j];
queue[j] = temp;
}
/**
* 1. 计算左子结点和右子节点
* 2. 左子节点和右子节点中较小的一个和父节点比较(子节点的index不能大于size)
* 3. 如果有比父节点小的就交换位置,直到到达叶子节点或者左子节点或右子节点没有小于父节点的就结束
* @param index 要下潜元素的索引
*/
public void siftDown(int index) {
int parent = index;
int left = index * 2 + 1;
int right = left + 1;
if (left < size && queue[left] < queue[parent]) {
parent = left;
}
if (right < size && queue[right] < queue[parent]) {
parent = right;
}
// 说明找到更小的了
if (parent != index) {
swap(parent, index);
siftDown(parent);
}
}
}