思想
堆排序把线性序列看作一个完全二叉树,通过构造大顶堆来实现排序,步骤如下:
1.从完全二叉树的最后一个子树开始从右到左,从下到上执行数据下沉算法,直到根节点为止,构造一个大顶堆;
2.交换线性序列的首位和末尾,待排序列长度-1,根节点执行数据下沉算法;
3.重复第2步,直到待排序列长度为0,此时序列为非递减排列.
核心算法
下沉算法为此排序思想的核心算法:
设下沉算法的指定节点为F,从节点F开始,查询节点F的左右子节点,取子节点中较大值为C,若节点C的值比节点F的值大则交换,然后更新F为C,对新的F重复执行上述步骤,直到F的子节点都不大于F或F没有子节点为止.
定义宏,在线性序列中求对应父子节点的序号:
// 计算对应节点序号
#define GET_FATHER_INDEX(index) (((index)-1)/2)
#define GET_LEFT_INDEX(index) ((((index)+1)*2)-1)
#define GET_RIGHT_INDEX(index) (((index)+1)*2)
数据下沉算法:
// 数据下沉
// 输入:待排序列,要下沉的父节点序号,待排序列长度
void heap_sink(int array[], int start,int size)
{
int left, right, temp, index;
for (int i = start; i < size;)
{
left = GET_LEFT_INDEX(i);
right = GET_RIGHT_INDEX(i);
if (right < size)
{
if (array[left] > array[right])
{
temp = array[left];
index = left;
}
else
{
temp = array[right];
index = right;
}
}
else if (left < size)
{
temp = array[left];
index = left;
}
else
{
// 序号超出堆范围
break;
}
// 子节点比父节点小
if (temp <= array[i])
break;
array[index] = array[i];
array[i] = temp;
i = index;
}
}
打印堆
以堆的形式打印,便于查看算法是否正确:
// 堆打印
void heap_print(int array[], int size)
{
int step = 1, space;
while (step < size) step *= 2;
for (int i = 1; i < step; i *= 2)
{
space = (step / 2/i)*6/2-3;
for (int j = 0; j < i; j++)
{
if (i + j -1< size)
{
for (int k = 0; k < space; k++) printf(" ");
printf("|%04d|", array[i + j-1]);
for (int k = 0; k < space; k++) printf(" ");
}
}
printf("\r\n");
}
}
调用
// 把序列初始化为大顶堆
void heap_init(int array[], int size)
{
// 最后一层没有子节点,所以从倒数第二层开始
// 当然,从最后一层开始也不影响结果
for (int i = size/2; i > 0; i--)
{
heap_sink(array, i - 1, size);
}
}
// 堆排序
void heap_sort(int array[], int size)
{
int temp;
heap_init(array, size);
heap_print(array, size);
for (int i = size; i > 0; i--)
{
temp = array[0];
array[0] = array[i-1];
array[i - 1] = temp;
heap_sink(array,0, i - 1);
}
}
在初始化为大顶堆之后打印了排序后的数据,可以看到已经是一个大顶堆,整体排序也没有问题.