堆积排序是另一种形式的选择排序。它涉及到 堆积 和 完全二叉树 的概念。
1. 堆积的定义
具有n个数据元素的序列 K = (k1, k2, k3, k4, . . . , kn); 当且仅当满足条件
k[ i ] >= k[ i*2 ] && k[ i ] >= k[ i*2+1]
或者
k[ i ] <= k[ i*2] && k [ i] <= k[ i*2+1]
i = (1, 2, 3, 4, . . . , n/2)
时称序列K为一个堆积(heap),简称堆。有时将满足第一种条件的堆积称为大顶堆积,满足第二种条件的堆积称为小顶堆积。大顶堆积的第一个元素具有最大值。下面的讨论针对大顶堆积而言。
若将序列的元素依次存放于一个一维数组中,并将此一维数组看做是一棵完全二叉树的顺序存储结构,则堆积可以与一棵完全二叉树对应,而且很容易确定该完全二叉树中任意结点i的孩子结点的位置(如果存在孩子结点),因此可以从另外一个角度给堆积下定义:
堆积是一棵完全二叉树,其中每个分支结点的值均大于或者等于其左子树和右子树(如果存在)中所有结点的值,并且该完全二叉树的根节点值最大。
2. 堆积排序算法
由于堆积所对应序列的第一个元素具有最大值(或者堆积对应的完全二叉树的根结点具有最大值),于是,堆积排序的核心思想可以描述为:
首先设法将原始序列构造成第一个堆积,这个堆积称为初始堆积,使得n个元素的最大值处于序列的第一个位置,然后将序列第一个元素(最大值)与序列最后一个元素交换位置。此后设法将序列的前 n - 1个元素组成的子序列设法构成一个新的堆积,这样又得到第2个最大值,将这个最大值与序列的第 n - 1 个元素交换位置;然后再将前 n - 2 个元素组成的序列设法构成一个新的堆积. . . . . . ,如此重复,最终把整个序列变换成一个按值有序的序列。简单的说,堆积排序的第 i 趟排序就是将序列的前 n - i + 1个元素组成的子序列转换成一个堆积,然后将堆积的第一个元素与堆积的最后那个元素交换位置。
堆积排序的步骤:
1. 将原始序列转换为一个初始堆积。
2. 交换堆积的第 1 个元素(最大值)与堆积最后那个元素的位置。
3. 将移走最大元素之后的剩余元素组成的序列再转换为一个堆积。
4. 重复步骤2和步骤3 (n-1) 次。
堆积排序的关键是将序列构建成堆积,包括如何将原始序列构造成一个初始堆积,以及如何将移走了最大元素之后的剩余元素组成的序列再构造成一个新的堆积。
先看 如何将移走了最大元素之后的剩余元素组成的序列再构造成一个新的堆积:
当以结点 i 为根结点的子树不是堆积,但结点 i 的左右子树都是堆积时,后面给出的代码中 adjust(int array[ ] , int i, int n)算法将把以结点 i 为根结点的子树调整为一个新的堆积,即完成 array[ i ] 与 array[ i * 2] 和 array[ i * 2 + 1 ] 中最大值者交换位置;若交换位置破坏了子树的堆积特性,则再对这棵子树重复这个交换位置的过程,直到以结点 i 为根结点的子树成为堆积。
void adjust(int array[], int i, int n)
{
int j, temp;
j = i * 2;
temp = array[i];
while( j <= n )
{
if( j < n && array[j] < array[j+1] )
++j;
if( temp >= array[j] )
break;
else
{
array[j/2] = array[j];
j = j * 2;
}
}
array[j/2] = temp;
}
再看 如何将原始序列构造成一个初始堆积:
可以先对 i = n / 2 结点为根结点的子树调用 adjust 函数使其成为一个堆积,然后循环执行 i = i - 1 直到 i = 1 ,依次对以结点 i 为根结点的子树调用 adjust 函数,这样整个序列就被构造成为一个初始的堆积。
for( j = n / 2 ; j >= 1 ; --j ) //将原始序列初始为堆积
adjust(array, j, n);
堆积排序的整个过程看代码:
#include <stdlib.h>
#include <stdio.h>
void adjust(int array[], int i, int n)
{
int j, temp;
j = i * 2;
temp = array[i];
while( j <= n )
{
if( j < n && array[j] < array[j+1] )
++j;
if( temp >= array[j] )
break;
else
{
array[j/2] = array[j];
j = j * 2;
}
}
array[j/2] = temp;
}
void heapSort(int array[], int n)
{
int j;
int temp;
for( j = n / 2 ; j >= 1 ; --j ) //将原始序列初始为堆积
adjust(array, j, n);
for( j = n - 1 ; j >= 1; --j ) //n-1趟排序,每次交换堆积的第一个元素和最后一个元素
{
temp = array[1];
array[1] = array[j+1];
array[j+1] = temp;
adjust(array, 1, j);
}
}
void print_array(int array[], int n)
{
int i;
for( i = 0 ; i < n ; ++i )
printf("%d ", array[i]);
printf("\n");
}
int main()
{
int array[] = {-1, 3, 5, 12, 78, 98, 23, 42, 16, 68, 120, 1, 2, 10, 980, 500};
int size = sizeof(array) / sizeof(int);
heapSort(array, size - 1);
print_array(array, size);
return 0;
}
这里:
1. array数组的array[ 0 ] 元素没有参加排序
2. 无论最坏情况还是平均情况,堆积排序的时间复杂度都是n*log2n,也就是说,堆积排序的快慢与初始序列的顺序无关
3. 堆积排序是不稳定排序
4. 堆积排序的空间复杂度是O(1), 用来记录排序数组的大小