赞
踩
堆排序最坏情况下、最好情况下以及平均情况下的时间复杂度均为为O(NlogN),空间复杂度是O(1).
堆排序的排序方式为内排序(In-place),顾名思义,内排序即所有的排序操作都在内存中完成,只是占用常数级内存,而不占用额外内存。
堆排序是不稳定的排序算法,当存在相同的元素时,不能保证先后顺序不变。
其实每个算法都有其核心逻辑,比如快速排序的核心就是Partition()函数,即基于某个元素分区。比如归并排序的核心就是MergeTwoArray()函数,即合并两个有序的数组。
那么堆排序的核心逻辑呢?它就是Heap_Adjust()函数,也可以称他为渗透调整函数,整个堆排序的过程中主要用它两次。第一次是依据原始无序序列构建大顶堆(升序)或者小顶堆(降序)。第二次是将堆顶的元素跟末尾的元素交换,交换后要调用它再次构建大顶堆或者小顶堆。
还需要知道的关于堆的几个特性:
1.堆首先是一颗完全二叉树。
2.大顶堆,小顶堆不再赘述。
3.最后一个非叶节点在哪里?怎么求?
一个公式LastRoot = length/2-1,在这里length是数组长度,LastRoot即为最后一个非叶节点。从最小的小堆开始排序!
4.堆中的节点映射在数组中有什么特点?
大顶堆(i即数组下标):array[i] >= array[2i+1] && array[i] >= array[2i+2]
小顶堆:array[i] <= array[2i+1] && array[i] <= array[2i+2]
可以这样解释,i元素在堆中的两个子节点为2i+1和2i+2.
基本思想:
将要排序的n个元素的序列构造成一个大顶堆,最大值即根节点。根节点与末尾元素交换,再次操作剩下的n-1个元素,重新构造堆,这便得到n个元素的次大值。反复执行,便得到有序序列。
步骤:
1.构造初始堆(即将要排序的n个元素的序列构造成一个大顶堆)
a.按顺序放好,放在堆的指定位置。
b.开始调整,从最后一个非叶节点开始调整(前面已给出怎么求)。从右到左,从下到上依次调整非叶节点的小堆。
有人会问,如何调整?这就涉及到了核心逻辑函数,Heap_Adjust(int *list,int roots,int length)函数。暂时不提,后面详解。
2.将堆顶元素与末尾元素交换后,调整剩下的n-1个元素,一直重复此步骤。即可。
a.交换堆顶元素和末尾元素,swap(a,b)
b.调用Heap_Adjust(int *list,int roots,int length)函数,调整即可。
重头戏,渗透调整函数解读,该函数有三个参数,数组,初始调整的root,要调整的数组长度。经过该函数调整后,响应的堆会变成期望的大顶堆或者小顶堆!
Heap_Adjust(int* list,int roots,int length){ //length为还需要调整的长度
int temp = list[roots]; //把要调整的小堆的root放入temp暂存
for(int j = 2*roots+1;j<=length;j=j*2+1) { //j=j*2+1,如果它的子节点非叶节点,就向下渗透!!!
if(list[j]<list[j+1]&&j<length){j++;} //第一个子节点比第二个子节点小,指针移向第二个子节点
if(temp>list[j]){break;} //root比第二个子节点还大的话,说明次堆不用调整,break
list[roots] = list[j]; //运行到这里说明需要交换,roots这个位置放入j的值
roots = j; //roots也换成j
}
list[roots] = temp; //根节点换到原来的子节点的位置
}
次要核心,堆排序的上层两步,1.建堆。2.交换调整。该函数调用两次Heap_Adjust()函数。如前面的步骤,代码实现
void Heap_Sort(int* list,int length) {
//步骤一
int LastRoot = length / 2 -1 ; //第一个非叶节点
for (LastRoot = length / 2 - 1; LastRoot >= 0; LastRoot--) { //依次调整
Heap_adjust(list, LastRoot, length - 1);
}
//步骤二
for (int i = length - 1; i >= 1; i--) {
swap(list[i], list[0]); //交换堆顶和末尾
Heap_adjust(list, 0, i - 1); //调整
}
}
- #include <iostream>
- using namespace std;
-
- void swap(int& a, int& b) {
- int temp = a;
- a = b;
- b = temp;
- }
-
- void Heap_adjust(int* list,int roots,int Size) {
- int temp = list[roots];
- for (int j = roots * 2 + 1; j <= Size; j = j * 2 + 1) {
- if (list[j] < list[j + 1] && j < Size) {
- j++;
- }
- if (temp > list[j]) {
- break;
- }
- list[roots] = list[j];
- roots = j;
- }
- list[roots] = temp;
- }
-
- void Heap_Sort(int* list,int length) {
- int LastRoot = length / 2 -1 ;
- for (LastRoot = length / 2 - 1; LastRoot >= 0; LastRoot--) {
- Heap_adjust(list, LastRoot, length - 1);
- }
- for (int i = length - 1; i >= 1; i--) {
- swap(list[i], list[0]);
- Heap_adjust(list, 0, i - 1);
- }
- }
-
-
-
- int main()
- {
- std::cout << "Hello World!\n";
- #define N 10
- int A[N] = { 21,32,4,32,65,2,43,44,33,22 };
- cout << "The origin list is:" << endl;
- for (int i = 0; i < N; i++) {
- cout << A[i] << " ";
- }
- Heap_Sort(A, 10);
- cout << endl << "After Sort:" << endl;
- for (int i = 0; i < N; i++) {
- cout << A[i] << " ";
- }
- #undef N
- return 0;
- }

其实,堆排序也不是很难,理解后还是挺简单的,而且个人觉得堆排序确实是排序界的当仁不让的老大!虽然也有缺点,比如说虽然最坏情况下比快排时间复杂度优越,但是由于对数组操作都是N到N/2之间操作,那么在大的数组的情况下,cpu的缓存利用率并不高,所以速度没有快排快。
熟练掌握了堆排序,在排序问题上,其实会有很大的帮助。快排,归并,希尔排序后面会补上。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。