堆排序引入了另一种算法设计技巧,使用了“堆”的数据结构来进行信息管理,堆是一个数组,可以看成一个近似的完全二叉树,如数组{1, 2, 3, 4, 5},可以看做为根节点为1,左孩子节点有2,右孩子节点为3的二叉树,4,5分别为2的左孩子和右孩子,因此我们通过计算可以求得:
PARENT(i) ((i - 1) / 2)
LEFT(i) (2 * i + 1)
RIGHT(i) ((2 * i) + 2)
在堆排序中还需要明白二叉堆的两种形式:最大堆和最小堆,最大堆性质是指除了根节点以外的节点都要满足:
A[PARENT(i)] >= A[I]
最小堆则满足:
A[PARENT(i)] <= A[I]
/** 给定位置,分别返回父节点,左孩子和右孩子的下标 */
#define PARENT(__I__) ((__I__ - 1) / 2)
#define LEFT(__I__) (2 * __I__ + 1)
#define RIGHT(__I__) ((2 * __I__) + 2)
/** 生成该节点的最大堆 @param array 给定的数组 @param index 给定的节点 @param size 堆的长度 */
void maxHeapify (int *array, int index, int size)
{
//TODO: 1.以index为父节点,获取左孩子和右孩子的下标
int left = LEFT(index);
int right = RIGHT(index);
//TODO: 2.用来存储父节点,左孩子和右孩子间的最大值的下标
int largest;
//TODO: 3.当仅包含父节点和一个孩子节点,且孩子节点大于父节点时,交换位置
if (size == 2)
{
if (array[0] > array[1])
{
int temp = array[1];
array[1] = array[0];
array[0] = temp;
}
}
//TODO: 4.当左右孩子的下标超过堆长度时,不做操作,这个也意味着index所在的节点是叶节点
if (left >= size || right >= size)
return;
//TODO: 5.比较三个中的最大值,赋值给largest
if (left < size && array[left] > array[index])
largest = left;
else
largest = index;
if (right < size && array[right] > array[largest])
largest = right;
//TODO: 6.如果父节点为最大值,则操作成功
if (largest == index)
return;
//TODO: 7.如果父节点不是最大值,则将父节点和最大值的位置交换
int temp = array[index];
array[index] = array[largest];
array[largest] = temp;
//TODO: 8.经过交换后,堆又发生了变化,还需要查看largest为父节点的树是否最大堆
maxHeapify(array, largest, size);
}
/** 将给定的堆转化为最大堆 @param array 给定的数组 @param size 堆的长度 */
void buildMaxHeap (int *array, int size)
{
//TODO 1.从size / 2的位置开始往前遍历各个节点,将所有子树转化为最大堆
for (int i = size / 2; i >= 0; i--)
maxHeapify(array, i, size);
}
/** 堆排序 @param array 给定的数组 @param size 堆的长度 */
void sort(int *array, int size)
{
//TODO 1.首先转化为最大堆
buildMaxHeap(array, size);
//TODO 2.从后往前遍历,其中做生成最大堆的操作,没遍历一次可以获得最大值
for (int i = size - 1; i > 0; i--)
{
//TODO 3.生成最大堆之后,首位必定是数组中的最大值,将其和当前下标的值交换,如此,每次操作过后,大的值都往后移动
int temp = array[i];
array[i] = array[0];
array[0] = temp;
//TODO 4.生成最大堆
maxHeapify(array, 0, i);
}
}