堆排序(基于堆的优先队列实现的)
首先介绍一下二叉堆及其相关的算法
1.堆
在二叉堆中,每个元素都有保证大于等于另两个特定位置的元素,根结点是堆有序二叉树中最大结点。
我们有完全二叉树来表示堆,先定下根结点。然后一层一层地由上向下、由左向右,每个结点的下方连接两个更小的结点,直到将N个结点全部连接完毕。
如下图就是一个二叉堆的结构
在一个堆中,位置k结点的父结点的位置为[k/2],而它的两个子节点的位置分别为2k和2k+1。这样可以通过利用数组的索引来存储这个二叉堆。
2.堆的算法
堆的算法:插入元素和删除最大元素
用长度为N+1的数组p[]来表示一个大小为N的堆,不使用p[0]。
在有序化的过程中有两种情况:
当在堆底加入一个新的元素时或某个元素的优先级上升,需要由下至上恢复堆的顺序;
当根结点替换成一个较小的元素时或某个结点的优先级下降,需要由上至下恢复堆的顺序。
(1)由下至上的堆有序化(上浮,swim)
当某个结点比它的父结点大的时候,我们需要通过交换这两个元素来恢复堆的有序性。交换以后这个结点有可能比它的父结点还大,那么继续这个过程,直到我们遇到更大结点或者到达根节点。只要记住位置k的父结点的位置为[k/2]。【代码会在下面程序中给出】
应用:插入元素,我们将新元素加到数组的末尾,增加堆的大小并让这个元素上浮到合适的位置
(2)由上至下的堆优化(下沉,sank)
当某个结点比它的两个子结点或者其中之一小的话,通过将它和它的两个结点中的较大者交换来恢复堆的有序性。交换以后这个结点有可能比它现在的两个子结点还小,那么继续这个过程,直到我们遇到比它小的两个结点或者到达堆底。这里记住,位置为k的结点的子结点位于2k和2k+1的位置,注意边界需要与N进行比较。【代码会在下面程序中给出】
应用:删除最大元素,我们从数组的顶端删除最大的元素并将数组的最后的一个元素放到顶端,减去堆的大小并让这个元素下沉到合适的位置。
3.堆排序
堆排序分为两个阶段:堆的构造阶段和下沉排序阶段
(1)构造阶段
堆中每个位置都是一个子堆的根节点,在子堆上使用下沉操作。
(2)下沉操作
删除堆中的最大的元素,然后放人最后元素的位置并缩小数组的大小。
void siwm(int* a, int n, int k) //上浮操作
{
while (k > 1)
{
if (a[k] > a[k / 2]) // 结点k和它的父结点比较大小
{
swap(a[k], a[k / 2]);
k = k / 2; // 继续向上比较直到到达堆顶或者遇到更大的父结点
}
else
break;
}
}
void sink(int* a, int n,int k) //下沉操作
{
while (2 * k < n)
{
int j = 2 * k;
if (a[j] < a[j + 1]) //比较k的两个子结点的大小
j++;
if (a[j] > a[k])
{
swap(a[j], a[k]); //结点k与最大的那个子结点交换
k = j; // 继续向下比较直到到达堆底或者遇到更小的两个结点
}
else
break;
}
}
void heapSort(int* a, int n)
{
for (int k = n / 2; k >= 1; k--) // 构造堆,使每个子堆符合堆有序
sink(a, n, k);
while (n > 1)
{
swap(a[1], a[n]); //删除最大的元素并将这个元素与数组尾部的元素替换
n--; // 缩小数组的大小
sink(a, n, 1); // 在重复上述过程
}
}