堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。
什么是完全二叉树呢?可以看下这篇文章:https://blog.csdn.net/hanzhen7541/article/details/99437854
所以说,堆排序是将数组当做完全二叉树、根据完全二叉树的特性来进行排序的一种算法。利用最大堆的性质,堆的根节点一定大于子节点的值。具体的步骤可以看如下的gif:
具体的过程如下:
-
首先,将所有的数字存储在堆中
-
按大顶堆构建堆,其中大顶堆的一个特性是数据将被从大到小取出,将取出的数字按照相反的顺序进行排列,数字就完成了排序
-
在这里数字 5 先入堆
-
数字 2 入堆
-
数字 7 入堆, 7 此时是最后一个节点,与最后一个非叶子节点(也就是数字 5 )进行比较,由于 7 大于 5 ,所以 7 和 5 交互
-
按照上述的操作将所有数字入堆,然后从左到右,从上到下进行调整,构造出大顶堆
-
入堆完成之后,将堆顶元素取出,将末尾元素置于堆顶,重新调整结构,使其满足堆定义
-
堆顶元素数字 7 取出,末尾元素数字 4 置于堆顶,为了维护好大顶堆的定义,最后一个非叶子节点数字 5 与 4 比较,而后交换两个数字的位置
-
反复执行调整+交换步骤,直到整个序列有序
代码实现如下(C++):
#ifndef HEAP_H_
#define HEAP_H_
#include <algorithm>
using namespace std;
class Heap_sort
{
public:
Heap_sort();
~Heap_sort();
void heap_sort(int array[], int len);
void buildMaxHeap(int array[], int len);//建堆
void heapify(int array[], int i, int len);//调整堆
};
Heap_sort::Heap_sort()
{
}
Heap_sort::~Heap_sort()
{
}
void Heap_sort::heap_sort(int array[], int len){
buildMaxHeap(array, len);
for (int i = len - 1; i > 0; i--){
swap(array[0], array[i]);//堆顶是最大的,升序排列应该放在最尾端
len--;
heapify(array, 0, len);
}
}
void Heap_sort::buildMaxHeap(int array[], int len){
//这里只能从下到上建堆
for (int i = len / 2; i >= 0; i--){
heapify(array, i, len);
}
}
void Heap_sort::heapify(int array[], int i, int len){//调整堆的函数
int left = 2 * i + 1;
int right = 2 * i + 2;
int maxindex = i;
//找根节点和左右子节点中最大的
if (left < len && array[left] > array[maxindex]){
maxindex = left;
}
if (right < len && array[right] > array[maxindex]){
maxindex = right;
}
if (i != maxindex){
swap(array[i], array[maxindex]);
heapify(array, maxindex, len);
}
}
#endif
主函数如下:
int main(){
int a[10] = { 4, 2, 1, 9, 22, 3, 44, 0, 10, -1 };
Heap_sort mysort;
mysort.heap_sort(a,10);
for (int i = 0; i < 10; i++){
cout << a[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
就可以看到输出了。
复杂度分析:
假设堆高h为一整数,堆有n个节点,那么h = log2n
该堆有2h个叶子节点,由于叶子节点是最底层,所以无需下沉
倒数第二层的节点有2h-1个,该层节点下沉到叶子节点至多需要比较2次(兄弟节点的比较,父节点与较大的兄弟节点的比较),交换1次(如果父节点小于子节点则交换)
倒数第三层有2h-2个非叶子节点,他们下沉到倒数第二层之后还要继续下沉到叶子节点(倒数第三层的节点可能比叶子节点还小,所以还要下沉到叶子节点),所以对倒数第二层的节点其下沉至多需要比较4次,交换2次。
以此类推,倒数第n层的下沉所需操作数 = 该层节点数 * 该层节点下沉所需的操作数 ,前者为2h-n+1,后者为3(n-1)。
所以根节点作为倒数第h+1层,它的下沉所需操作数就为2h-(h+1)+1 * 3 (h+1-1) = 20 * 3h。
将各层的下沉所需操作数从上至下相加,就是构造整个有序堆的所需操作数:3 ( 20h + 21(h-1) + 22(h-2) +…2h-1 ),设该式为a,则2a - a = 3 ( 21 + 22 +…2h - h ),根据等比序列的求和公式,我们可以知道a = 3 ( 2h+1 - 2 - h),代入h,得a = 3(2n - 2 - log2n) ≈ 6n。
综上,建有序堆的时间复杂度是O(n)。
不断交换堆顶元素和堆底元素
共进行n-1次交换,所需操作数为n-1
并且每次交换都会导致根节点不再是堆中的最大元素,所以需要对新的根节点进行下沉操作,所需操作数为3(n-1)h,即3(n-1)log2n
综上,该过程所需操作数为n-1 + 3(n-1)log2n = (n-1)(3log2n + 1),时间复杂度即为O(nlogn)
所以时间复杂度是O(n logn)