排序算法千千万,今天来讲堆排序!!
关于「详解快排及其应用」,可见 详解快排及其应用
关于「详解归并排序及其应用」,可见 详解归并排序及其应用
「快排」和「归并」都是一次性将所有元素排好顺序,但有一些场景,我们每次可能只需要获得最小值即可
「堆排序」就是这样的特点,并非一次性全部排好序,而只有堆顶元素是最小值,每次需要获取最小值时,只需要去堆顶取即可
借鉴 Java 中的优先队列,我们的堆只实现几个核心的 API
xxxxxxxxxx
public MyPQ(); // 默认构造函数
public MyPQ(int c); // 带初始容量的构造函数
public void offer(int x); // 插入一个元素 x
public int poll(); // 弹出堆顶元素
public int peek(); // 获取堆顶元素
public int size(); // 获取堆的大小
定义:当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序
根据定义可知,根结点是堆有序的二叉树中的最大结点
定义:二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存 (不使用数组的第一个位置)
根据定义可知,在一个堆中,位置
我们用长度为N + 1
的私有数组pq[]
来表示一个大小为N
的堆,我们不会使用pq[0]
,堆元素放在pq[1]
至pq[N]
中
注意:二叉堆可以分为大根堆和小根堆,其中大根堆就是上面定义中介绍的,而小根堆是每个节点都小于等于它的两个子节点,即:根结点是最小结点。本篇文章基于「小根堆」来实现!!!
每次插入一个新的元素时,首先将该元素放在数组的最后一个空位置,然后执行由下至上的堆有序化操作,即「上浮」
假设一个有序堆,如下图所示:
此时,插入一个新的元素4
,如下图所示:
然后执行由下至上的堆有序化操作,如下图所示:
由下至上的堆有序化 (上浮) 实现代码如下:
xxxxxxxxxx
// k 是需要上浮的元素下标
private void up(int k) {
// 未到根节点,且当前元素小于其父节点
while (k > 1 && pq[k] < pq[k / 2]) {
// 交换
swap(k, k / 2);
// k 上浮
k >>= 1;
}
}
每次删除堆顶元素时,首先将该元素与最后一个元素交换,然后将最后一个元素删除,最后执行由上至下的堆有序化操作,即「下沉」
在上面插入元素后的基础上删除堆顶元素,下图所示:
然后执行由上至下的堆有序化操作,如下图所示:
由上至下的堆有序化 (下沉) 实现代码如下:
xxxxxxxxxx
// k 是需要下沉的元素下标
private void down(int k) {
// 未到最后一个元素
while (2 * k <= size) {
// 左孩子下标
int j = 2 * k;
// 选出左右孩子中最小的一个
if (j < size && pq[j] > pq[j + 1]) j++;
// 如果左右孩子中最小的还比当前节点大,则 break
if (pq[k] < pq[j]) break;
// 否则交换
swap(k, j);
// k 下沉
k = j;
}
}
核心的两个操作介绍完了,下面给出完整的代码模版:(小根堆)
xxxxxxxxxx
class MyPQ {
private static final int CAPACITY = 11;
private int[] pq;
private int size;
public MyPQ() {
pq = new int[CAPACITY];
size = 0;
}
public MyPQ(int c) {
pq = new int[c + 1];
size = 0;
}
public void offer(int x) {
// 加到末尾,size + 1
pq[++size] = x;
// 上浮最后一个元素
up(size);
}
public int poll() {
// 记录堆顶元素
int x = pq[1];
// 交换
swap(1, size--);
// 下沉第一个元素
down(1);
return x;
}
public int peek() {
return pq[1];
}
public int size() {
return size;
}
private void up(int k) {
while (k > 1 && pq[k] < pq[k / 2]) {
swap(k, k / 2);
k >>= 1;
}
}
private void down(int k) {
while (2 * k <= size) {
int j = 2 * k;
if (j < size && pq[j] > pq[j + 1]) j++;
if (pq[k] < pq[j]) break;
swap(k, j);
k = j;
}
}
private void swap(int i, int j) {
int t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
}