目录
认识堆
堆的定义
计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树(除了最后一层,其他层的节点都是满的,且最后一层的节点从左至右是连续的二叉树)来实现
大顶堆

小顶堆

堆的特性
- 大顶堆中,任意节点C和它的父节点P满足:P.value >= C.value
- 小顶堆中,任意节点C和它的父节点P满足:P.value <= C.value
- 最顶层的节点没有父节点,称为根节点
父节点和子节点之间的位置关系
已知子节点的位置,求其父节点的位置
- 索引从0开始存储节点数据,索引为i的节点的父节点的索引为:(i-1) / 2,i > 0
- 索引从1开始存储节点数据,索引为i的节点的父节点的索引为:i / 2,i > 1
已知父节点的位置,求其左右子节点的位置
- 索引从0开始存储节点数据,索引为i的节点的左子节点的索引为:2i + 1;右子节点的索引为:2i + 2,左右子节点的索引都 < size
- 索引从1开始存储节点数据,索引为i的节点的左子节点的索引为:2i;右子节点的索引为:2i + 1,左右子节点的索引都 < size
自实现堆
定义接口
java
package algorithm.heap;
/**
* 接口定义
*/
public interface Heap {
/**
* 堆化
*/
void heapIfy(int[] values);
/**
* 添加元素
*/
boolean offer(int value);
/**
* 移除堆顶元素
*/
int poll();
/**
* 替换堆顶元素
*/
int replace(int value);
/**
* 查看堆顶元素
*/
int peek();
}
定义抽象类
java
package algorithm.heap;
import java.util.Arrays;
/**
* 定义抽象类
*/
public abstract class AbstractHeap implements Heap {
protected int size;
protected int[] container;
public static final int CONTAINER_CAPACITY = 128;
@Override
public void heapIfy(int[] values) {
// check param
if (values == null) {
throw new NullPointerException();
}
if (values.length == 0) {
return;
}
container = values;
size = container.length;
// 找到最后一个非叶子节点的位置
int lastNonLeaf = (size - 1) >> 1;
for (int i = lastNonLeaf; i >= 0; i--) {
// 从最后一个非叶子节点开始到root节点依次执行下潜
siftDown(i);
}
}
/**
* 下潜:交给子类去实现,不同的实现类有不同的算法
*/
protected abstract void siftDown(int parent);
/**
* 交换i位置和j位置上的值
*/
protected void swap(int i, int j) {
if (i == j) {
return;
}
container[i] = container[i] ^ container[j];
container[j] = container[i] ^ container[j];
container[i] = container[i] ^ container[j];
}
@Override
public boolean offer(int value) {
// 懒加载
if (container == null) {
container = new int[CONTAINER_CAPACITY];
}
// 堆已满
if (size == container.length) {
return false;
}
container[size++] = value;
// 执行上浮操作
siftUp(size - 1, value);
return true;
}
/**
* 上浮
*/
private void siftUp(int child, int value) {
// 找到新元素的存放位置
int targetIndex = getUpTargetIndex(child, value);
container[targetIndex] = value;
}
/**
* 交给子类去实现,不同的实现类有不同的实现方式
*/
protected abstract int getUpTargetIndex(int child, int val);
@Override
public int poll() {
int top = peek();
// 交换顶堆元素和最后一个元素,并移除
swap(0, --size);
// 执行下潜操作,使整体满足堆的特性
siftDown(0);
return top;
}
@Override
public int replace(int value) {
if (size == 0) {
throw new RuntimeException("堆已空!");
}
int top = container[0];
container[0] = value;
// 执行下潜操作,使整体满足堆的特性
siftDown(0);
// 返回原来的堆顶元素
return top;
}
@Override
public int peek() {
if (size == 0) {
throw new RuntimeException("堆已空!");
}
// 堆顶元素
return container[0];
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOfRange(container, 0, size));
}
}
大顶堆(具体实现类)
java
package algorithm.heap;
/**
* 大顶堆实现类
*/
public class MaxHeap extends AbstractHeap {
/**
* 大顶堆下潜
* 将父节点的值和它的两个子节点的值进行比较,和比它大的且值为最大的那个子节点交换,直到没有比它大的子节点
*/
@Override
protected void siftDown(int parent) {
// 通过父节点找子节点
int leftChild = (parent << 1) + 1;
int rightChild = leftChild + 1;
// 假设值最大的是父节点
int max = parent;
// 如果左子节点值大
if (leftChild < size && container[max] < container[leftChild]) {
max = leftChild;
}
// 如果右子节点值大
if (rightChild < size && container[max] < container[rightChild]) {
max = rightChild;
}
// 最大值不是父节点值,需要交换
if (max != parent) {
swap(parent, max);
// 继续下潜
siftDown(max);
}
}
@Override
protected int getUpTargetIndex(int child, int value) {
// 递归结束条件一
if (child == 0) {
return child;
}
// 通过子节点获取父节点位置
int parent = (child - 1) >> 1;
// 递归结束条件二
if (container[parent] >= value) {
return child;
}
container[child] = container[parent];
child = parent;
return getUpTargetIndex(child, value);
}
}
小顶堆(具体实现类)
java
package algorithm.heap;
/**
* 小顶堆实现类
*/
public class MinHeap extends AbstractHeap {
/**
* 小顶堆下潜
* 将父节点的值和它的两个子节点的值进行比较,和比它小的且值为最小的那个子节点交换,直到没有比它小的子节点
*/
@Override
protected void siftDown(int parent) {
// 通过父节点找子节点
int leftChild = (parent << 1) + 1;
int rightChild = leftChild + 1;
// 假设值最小的是父节点
int min = parent;
// 如果左子节点值小
if (leftChild < size && container[min] > container[leftChild]) {
min = leftChild;
}
// 如果右子节点值小
if (rightChild < size && container[min] > container[rightChild]) {
min = rightChild;
}
// 最大值不是父节点值,需要交换
if (min != parent) {
swap(parent, min);
// 继续下潜
siftDown(min);
}
}
@Override
protected int getUpTargetIndex(int child, int value) {
// 递归结束条件一
if (child == 0) {
return child;
}
int parent = (child - 1) >> 1;
// 递归结束条件二
if (container[parent] <= value) {
return child;
}
container[child] = container[parent];
child = parent;
return getUpTargetIndex(child, value);
}
}
总结
通过对大顶堆和小顶堆的实现,不难发现有几个方法是比较关键的
- 建堆/堆化:heapIfy
- 下潜:siftDown
- 上浮:siftUp
堆的应用场景
这里简单介绍几个堆的应用场景
Java中的优先级队列(PriorityQueue)
Java中提供的优先级队列的底层就是使用堆来实现的,这里简单看下源码,通过offer和poll方法就能看到堆的上浮(siftUp)和下潜(siftDown)操作
java
package java.util;
// ...
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
// ...
transient Object[] queue;
// 添加元素
public boolean offer(E e) {
// ...
if (i == 0)
queue[0] = e;
else
// 上浮
siftUp(i, e);
return true;
}
// 移除堆顶元素
public E poll() {
// ...
if (s != 0)
// 下潜
siftDown(0, x);
return result;
}
// ...
}
堆排序
核心思路
堆排序的思路如下
- heapIfy堆化建立堆
- 交换堆顶和堆底元素(最值被交换到了堆底),缩小并执行下潜操作调整堆
- 重复上一步操作直至堆中只剩下一个元素
代码实现
java
package algorithm.heap;
public class SortUtil {
/**
* 堆排序
*/
public static void heapSort(int[] data) {
if (data == null || data.length == 1) {
return;
}
// 1、heapIfy堆化建立堆
heapIfy(data);
// 2、交换堆顶和堆底元素(最值被交换到了堆底),缩小并执行下潜操作调整堆
// 3、重复上一步操作直至堆中只剩下一个元素
int size = data.length;
while (size > 1) {
swap(data, 0, size - 1);
size--;
siftDown(0, data, size);
}
}
/**
* 堆化
*/
private static void heapIfy(int[] data) {
int size = data.length;
int lastNonLeafIndex = (size - 1) >> 1;
for (int i = lastNonLeafIndex; i >= 0; i--) {
siftDown(i, data, size);
}
}
/**
* 下潜
*/
private static void siftDown(int parent, int[] data, int size) {
int childLeft = (parent << 1) + 1;
int childRight = childLeft + 1;
int maxIndex = parent;
if (childLeft < size && data[maxIndex] < data[childLeft]) {
maxIndex = childLeft;
}
if (childRight < size && data[maxIndex] < data[childRight]) {
maxIndex = childRight;
}
if (maxIndex != parent) {
swap(data, parent, maxIndex);
siftDown(maxIndex, data, size);
}
}
/**
* 交换数组中索引i和索引j位置上的元素
*/
private static void swap(int[] data, int i, int j) {
if (i == j) {
return;
}
data[i] = data[i] ^ data[j];
data[j] = data[i] ^ data[j];
data[i] = data[i] ^ data[j];
}
}
求第K个最大元素(LeetCode-215题)
题解
java
class Solution {
public int findKthLargest(int[] nums, int k) {
// check param
if (nums == null || nums.length < k) {
throw new IllegalArgumentException();
}
// 这里使用小顶堆来解题
MinHeap minHeap = new MinHeap();
// 将数组中前K个元素堆化
minHeap.heapIfy(Arrays.copyOfRange(nums, 0, k));
// 接下来的元素,只要是满足大于堆顶元素,就执行小顶堆的replace方法替换堆顶元素
for (int i = k; i < nums.length; i++) {
int num = nums[i];
if (num <= minHeap.peek()) {
continue;
}
minHeap.replace(num);
}
// 此时堆顶元素就是数组中第K个最大元素
return minHeap.peek();
}
/**
* 小顶堆
*/
private static class MinHeap {
private int[] container;
private int size;
/**
* 堆化
*/
public void heapIfy(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException();
}
container = array;
size = container.length;
int lastNonLeaf = (size - 1) >> 1;
for (int i = lastNonLeaf; i >= 0; i--) {
siftDown(i);
}
}
/**
* 下潜
*/
private void siftDown(int parent) {
int leftChild = (parent << 1) + 1;
int rightChild = leftChild + 1;
int minIndex = parent;
if (leftChild < size && container[minIndex] > container[leftChild]) {
minIndex = leftChild;
}
if (rightChild < size && container[minIndex] > container[rightChild]) {
minIndex = rightChild;
}
if (minIndex != parent) {
swap(minIndex, parent);
siftDown(minIndex);
}
}
/**
* 交换两位置上的值
*/
private void swap(int i, int j) {
if (i == j) {
return;
}
container[i] = container[i] ^ container[j];
container[j] = container[i] ^ container[j];
container[i] = container[i] ^ container[j];
}
/**
* 替换堆顶元素
*/
public void replace(int value) {
container[0] = value;
siftDown(0);
}
/**
* 查看堆顶元素
*/
public int peek() {
return container[0];
}
}
}
测试结果
