堆排序是十大排序算法(插入,冒泡,选择,希尔,归并,快排,堆,计数,桶,基数)中的一种,而看了看C++标准库里面的sort函数中是依据状态进行选择哪一种排序,例如对数据先进行快排,快排分成了小数据的时候使用插入,快排递归过深时选择堆排序。总共有插入,快排,堆排。堆排序的重要性不言而喻了吧。插入排序就不说了,接下来主要比较快排和堆排序。
堆排序的时间复杂度与数组的初始状态是无关的!即堆排序的最好,最坏和平均时间复杂度都是O(nlogn),也就是这点跟快排不同,快排平均时间复杂度为O(nlogn)(都是nlogn但是各系数不同,快排通常更快),在最坏(数组正序)的情况下复杂度会达到O(n^2)。
堆排序无需额外的空间,所以空间复杂度是O(1),而快排的空间复杂度是O(logn)且会存在递归过深导致的内存溢出。
说了堆排序的性质,来看看堆排序的概念及过程吧。
堆排序是基于完全二叉树的结构来进行排序的,但是实际是存储在数组当中,说着怪怪的,看了代码就懂了。堆分为大根堆与小根堆,大根堆就是每个节点都大于他的子节点。
首先塞入无序待排序的数组,实际数组数据结构为:
int arr[] = {91,60,96,13,35,65,46,65,10,30,20,31,77,81,22};
但是我们要理解成下图:
图源于1.7 堆排序 | 菜鸟教程,刚好建立完成大根堆的过程图,后面的图也是基于此。
堆排序总体流程:
(1)初始化,将无序的数组从下到上建立成堆(此为大根堆)
(2)将堆顶(96)与最后一个节点(22)交换位置,最后一个节点(96)弹出(也可以理解成,将堆顶弹出,让最后一个节点接替堆顶)。堆顶数(22)再从上到下交换移动下去。
也就是说我们需要实现的功能其实就两点,一步是从下到上建堆,一步是堆顶与最后一节点交换后再重新调整使之符合大根堆。上代码(代码基于菜鸟教程):
#include <iostream>
#include <algorithm>
using namespace std;
void max_heapify(int arr[], int start, int end)//此函数是判断大小并执行交换
{
int dad = start;//获取父节点的下标
int son = dad * 2 + 1;//左节点,因为下标0开始所以需要+1
while (son <= end) //子节点不能超出范围,就是判断若无子节点直接退出
{
if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
++son;//指向右节点
if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完毕,直接跳出函数
return;
else //否则交换父子节点再继续子节点和孙节点比较
{
swap(arr[dad], arr[son]);
dad = son;//父节点指向子节点
son = son * 2 + 1;//子节点指向孙节点 ,也就是这个点还需要跟孙节点进行比较
}
}
}
void heap_sort(int arr[], int len)
{
//初始化,i从最后一个父节点开始调整,从46开始 ,也就是从下到上进行调整,
for (int i = len / 2 - 1; i >= 0; --i)//15个元素,最后一个父节点下标是6
max_heapify(arr, i, len - 1);//数组下标是0-14,所以end是len-1
//执行到这一步就已经符合大根堆的结构了
//使顶堆跟最后一个元素交换,这里--i达到弹出的效果,也就是i+1到len-1的范围都是已经排好序的,再重新调整使符合大根堆,直到排序完毕
for (int i = len - 1; i > 0; --i)
{
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main()
{
int arr[] = {91,60,96,13,35,65,46,65,10,30,20,31,77,81,22};//15个元素,数组下标是0-14
int len = (int) sizeof(arr) / sizeof(*arr);//获取数组元素个数
heap_sort(arr, len);
for (int i = 0; i < len; ++i)
cout << arr[i] << ' ';//输出排序后的数组,debug看一眼
cout << endl;
system("pause");
}
两个函数,max_heapify()调整堆,heap_sort()堆排序,
heap_sort()内就两个步骤,建立大根堆,弹出最大值并调整大根堆。具体实现看看代码注释吧。
执行结果为:
10 13 20 22 30 31 35 46 60 65 65 77 81 91 96
当然,从结果上可看不出堆排序的过程和优势,还是得理解过程才能体现出区别。