堆排序是一种树形选择排序方法,其优点是:在排序过程中,将 L [ 1…n ]
视为一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。
两种堆:
-
大顶堆(大根堆),在大顶堆中,最大元素存放在根节点中,且对其任一非根节点,它的值小于等于其双亲结点的值,即 L [ i ] >= L [ 2i ] 且 L [ i ] >= L [ 2i+1 ]
-
小顶堆(小根堆),在小顶堆中,最小元素存放在根节点中,且对其任一非根节点,它的值大于等于其双亲结点的值,即 L [ i ] <= L [ 2i ] 且 L [ i ] <= L [ 2i+1 ]
初始建堆是从最后一个拥有叶子节点的分支节点开始向下调整堆的,即从 L [ n / 2 ] 开始,直到 L [ 1 ] 结束 。
堆的存储结构:因为在访问孩子节点时需要用到随机访问,故采用顺序存储结构,至于为什么不用二叉链表来存储,因为堆的空间复杂度为 O ( 1 ) ,当给出待排序表的时候,不可能以二叉链表的形式来给出待排序队列,所以即使堆可以用二叉链表来存储,但是这就相当于空间复杂度为 O ( n ) ,这个二叉链表是独立于待排序表的 。
注意:不能对堆采用层次遍历来得到排序结果,这是得不到的,最终结果还是得用到交换,逐个输出堆顶元素。
堆的插入和删除:
- 堆的删除和堆的输出是相同的道理,堆的输出相当于删除堆顶元素,两者只需要将待删除元素与最后一个元素 L [ len ] 进行交换,然后对 L [ 1…len-1 ] 进行向下调整即可(所谓向下调整,是指遍历时,是从 k 节点以 k *= 2 逐层向下遍历的)
- 堆的插入,是将待插入元素,插入到堆的最后一个元素,即作为 L [ len+1 ] 存在,然后向上调整堆(所谓向上调整,就是指遍历时,是从 k 节点以 L [ k/2 ] 逐层向上遍历的)
向上调整代码(大顶堆)#include<bits/stdc++.h> using namespace std; #define MAX_SIZE 9 typedef int ElemType; void AdjustDown(ElemType A[],int k,int len){ int i; A[0]=A[k];//A[0]起到暂存数据元素的作用 for(i=2*k;i<=len;i*=2){//起到递归的作用,会将以i为根节点的子树全部都调整完 if(i<len&&A[i]<A[i+1]) i++; if(A[0]>=A[i]) break; else { A[k]=A[i]; k=i; } } A[k]=A[0]; } void BuildMaxHeap(ElemType A[],int len){//构建大顶堆 for(int i=len/2;i>0;i--){ AdjustDown(A,i,len); } } void AdjustUp(ElemType A[],int k){//向上调整代码 A[0]=A[k]; int i=k/2; while(i>0&&A[i]<A[0]){ A[k]=A[i]; k=i; i=k/2; } A[k]=A[0]; } int main(){ ElemType A[]={-1,9,45,78,32,17,65,53,87}; BuildMaxHeap(A,MAX_SIZE-2);//对1~len-1进行堆排 for(int i=1;i<MAX_SIZE;i++){ cout<<A[i]<<' '; } cout<<endl; AdjustUp(A,MAX_SIZE-1);//将len插入后进行向上调整 for(int i=1;i<MAX_SIZE;i++){ cout<<A[i]<<' '; } return 0; }
算法实现(大顶堆):建立大顶堆,并且向下调整堆,最后交换,实现原地排序,空间复杂度O(1),下面算法中并未涉及向上调整,即堆的插入。
/*
堆排序:
时间复杂度->最好情况:O(nlogn)、平均情况:O(nlogn)、最坏情况:O(nlogn)
空间复杂度->O(1)
是否稳定->否
*/
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 9
typedef int ElemType;
void AdjustDown(ElemType A[],int k,int len){
int i;
A[0]=A[k];//A[0]起到暂存数据元素的作用
for(i=2*k;i<=len;i*=2){//起到递归的作用,会将以i为根节点的子树全部都调整完
if(i<len&&A[i]<A[i+1]) i++;
if(A[0]>=A[i]) break;
else {
A[k]=A[i];
k=i;
}
}
A[k]=A[0];
}
void BuildMaxHeap(ElemType A[],int len){//构建大顶堆
for(int i=len/2;i>0;i--){
AdjustDown(A,i,len);
}
}
void HeapSort(ElemType A[],int len){
int i;
BuildMaxHeap(A,len);
for(i=len;i>1;i--){
swap(A[i],A[1]);
AdjustDown(A,1,i-1);
}//这就是堆排,将待排序表建堆,然后逐个输出,所谓输出也只是,交换,因为最终目的是原地排序,输出还是在原表,只不过有序
//这时,交换,这种输出方式,结合建堆的概念,即完全二叉树编号这个概念,即可在堆排后,待排序表为有序表
}
int main(){
ElemType A[]={-1,87,45,78,32,17,65,53,9};
HeapSort(A,MAX_SIZE-1);
for(int i=1;i<MAX_SIZE;i++){
cout<<A[i]<<' ';
}
return 0;
}