概述
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法描述
- 将待排序的元素序列(R1,R2….Rn)构建成最大堆,此堆为初始的无序区。(关于最大堆的详细构建过程请点这里)
- 将最大堆的堆顶元素R1(当前堆树中的最大值)与最后一个元素Rn交换。此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R1可能违反了最大堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)重新调整为最大堆,然后再次将R1与无序区最后一个元素交换,得到新的无序区(R1,R2,……Rn-2)和有序区(Rn-1,Rn)。不断重复此过程直到无序区的元素个数为1;
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
比如序列:{ 10, 4, 6, 4 },堆顶元素是10,堆排序下一步将10和第二个4进行交换,得到序列 { 4, 4, 6, 10 },再进行堆调整得到{ 6, 4, 4, 10 },重复之前的操作最后得到{ 4, 4, 6, 10 }从而改变了两个4的相对次序。
堆排序C/C++代码:
void Swap(int arr[], int i, int j)
{
int temp=arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 将父节点为aar[i]的子树调整为最大堆
* @param arr 堆数组
* @param size 堆数组长度
* @param i 节点索引
*/
void AdjustHeap(int arr[],int size,int i)
{
int left_child = 2*i+1; //左子节点索引
int right_child = 2*i+2; //右子节点索引
int max = i; //选出当前结点与其左右孩子三者之中的最大值
if (left_child < size && arr[left_child] > arr[max]) {
max = left_child;
}
if (right_child < size && arr[right_child] > arr[max]) {
max = right_child;
}
if (max != i) {
Swap(arr, i, max); //将最大值节点与父节点互换
AdjustHeap(arr, size, max); //递归调用,继续从当前节点向下进行堆调整
}
}
/**
* 根据输入的数组构建一个最大堆
* @param arr 堆数组
* @param size 堆数组长度
* @return 堆数组长度
*/
int BuildMaxHeap(int arr[], int size) {
//对每一个非叶节点开始向下进行最大堆调整
for (int i = size / 2 - 1; i >= 0; i--)
{
AdjustHeap(arr, size, i);
}
return size;
}
/**
* 堆排序
* 最差时间复杂度 O(nlogn)
* 最优时间复杂度 O(nlogn)
* 平均时间复杂度 O(nlogn)
* 空间复杂度 O(1)
* 稳定性 不稳定
* @param arr 数组
* @param size 长度
*/
void HeapSort(int arr[], int size)
{
int heap_size = BuildMaxHeap(arr, size);
while (heap_size > 1) { //堆(无序区)元素个数大于1,未完成排序
//将堆顶元素与堆的最后一个元素进行交换,并从堆中去掉最后一个元素
//由于交换后的新的堆顶可能违反堆的性质,所以需要对该堆进行重新调整为最大堆
//此处交换操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法
Swap(arr, 0, --heap_size);
AdjustHeap(arr, heap_size, 0); // 从新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)
}
}
元素序列{ 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }的堆排序流程示意图如下:
堆排序后的输出的元素序列为:{9,8,7,6,5,4,3,2,1,0}