堆排序引论:既然是堆排序,那么我们必须知道堆是什么?
堆是具有以下性质的完全二叉树:每个节点的值都大于等于左右孩子节点的值,称为大顶堆;或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
根据堆的定义可知,根节点一定是堆中所有节点最大(小)着。较大(小)的节点靠近节点(但也不绝对)如图:
如果将上图按照层次遍历存入数组,那么一定满足下面的关系:
堆排序算法
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
如图:
知道了这个过程,那么我们现在主要需要解决两个问题:
- 如何由一个无需队列构建成一个堆?
- 如果在输出堆顶元素后,调整剩余元素成为一个新的堆?
在构建大顶推之前,我们再来看一下大顶堆和小顶堆的概念:每个节点的值都大于等于左右孩子节点的值,称为大顶堆;或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆
构建大顶堆时,我们从哪里开始构建呢?当然是先构建最小的大顶堆,即最后一个非叶子结点,如果此完全二叉树有n个元素的话,那么最后一个节点的就是第n/2-1个(完全二叉树的性质),此节点之前的元素都是非叶子节点,之后的元素都是叶子结点。
那么构建大顶堆的过程就是这样滴(sort函数时具体的构建函数,稍后会实现):
for (int i = len/2-1; i >=0; i--)
{
Sort(ar, i,len);
}
构建完大顶堆之后,我们需要做的就是,交换元素,然后再构建大顶堆,以此类推,直到完全有序(因为我们每次交换元素之后,之前的根节点元素就不再参与构建大顶堆了,因此大顶堆的规模是逐渐减一的)。
那么过程就是这样滴:
for (int i = len - 1; i > 0; i--)
{
int temp = ar[0];
ar[0] = ar[i];
ar[i] = temp;
Sort(ar, 0, i);
}
构建大顶堆的代码:
void Sort(int* ar, int i,int n)
{
int tmp = ar[i];
for(int k = i * 2 + 1; k < n; k = k * 2 + 1)
{
if (k + 1 < n && ar[k] < ar[k + 1])//判断左右孩子哪个大,ar[k]始终是较大的那个
k++;
if (ar[k] > tmp)
{
ar[i] = ar[k];
i = k;
}
else
break;
ar[i] = tmp;
}
}
完整图解:
完整图解:
void Sort(int* ar, int i,int n)
{
int tmp = ar[i];
for(int k = i * 2 + 1; k < n; k = k * 2 + 1)
{
if (k + 1 < n && ar[k] < ar[k + 1])
k++;
if (ar[k] > tmp)
{
ar[i] = ar[k];
i = k;
}
else
break;
ar[i] = tmp;
}
}
void HeapSort(int* ar, int len)
{
assert(ar != NULL);
for (int i = len/2-1; i >=0; i--)
{
Sort(ar, i,len);
}
for (int i = len - 1; i > 0; i--)
{
int temp = ar[0];
ar[0] = ar[i];
ar[i] = temp;
Sort(ar, 0, i);
}
}