最简单的选择排序,的时间复杂度为N2;其主要操作就是比较,从减少次数出发来改进算法,书上提到了树形选择排序,但是有需要的辅助空间较多等缺点,为了弥补,威洛姆斯提出了另一种形式的选择排序——堆排序,
根据堆的定义,我们可以把一个序列对应的一维数组看成是一个完全二叉树,堆顶为最大值或者最小值,也就是完全二叉树的根,。
堆排序我看了好久,最主要的原因就是对完全二叉树的性质掌握不深,
若根的起点标号从0开始。
则一个结点i,它的双亲结点的标号为(i-1)/2 ,他左孩子的标号是(i*2+1),它右孩子的标号是(i*2+2).
若根的起点标号从1开始,
则一个结点i,它的双亲结点的标号为 i/2 ,他左孩子的标号是i*2,它右孩子的标号是i*2+1
不管怎么说,又发现一种神奇结构,堆
直接上代码了:
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<cstdlib>
#include<ctime>
using namespace std;
//第十章 内部排序
//选择排序-堆排序
//待排记录数据的数据结构
#define maxsize 100000
struct redtype{
int key;
};
struct Sqlist
{
redtype r[maxsize];
int length;
};
int buildsq(Sqlist &sq){
int x;
cin>>x;
sq.length=x+1;
for(int i=1;i<=x;++i)
{int p;
cin>>p;
sq.r[i].key=p;
}
return x;
}
void Headadjust(Sqlist &sq,int s,int m){//调整s到序列最后位置m的元素
int rc=sq.r[s].key;//记录下当前需要调整的关键字
for(int j=2*s;j<=m;j*=2){//先左孩子跟右孩子比较,s的左孩子就是2*s,,而且沿着key较大的孩子结点向下涮选
if(j<m&&sq.r[j].key<sq.r[j+1].key) {++j;}//我做的是最大堆,所以 j为key较大的记录的标号
if(rc>sq.r[j].key) {break;}//当前关键字大于key较大的了,不用调整
sq.r[s].key=sq.r[j].key;s=j;//需要调整,s位置赋值较大的,
}
sq.r[s].key=rc;//插入
}
void Heapsort(Sqlist &sq){//算法10.11
//先堆化一个数组。
for(int j=(sq.length-1)/2;j>0;--j){//对于叶子节点来说,可以认为它已经是一个合法的堆了,只需要从最后一个非终端结点开始调整
Headadjust(sq, j ,sq.length-1);//注:最后一个非终端结点的标号是关键字个数/2,
}
for(int i=sq.length-1;i>1;--i){//依次取出堆顶,最大值放在length-1位置,
int tem=sq.r[1].key;//记录堆顶元素
sq.r[1].key=sq.r[i].key;//把i位置上的元素放入堆顶
sq.r[i].key=tem;//i位置上放入堆顶元素
Headadjust(sq,1,i-1);//1 到 i-1 重新调整
}
}
int main(){
Sqlist sq;
int t= buildsq(sq);
Heapsort(sq);
for(int i=1;i<=t;++i){
cout<<sq.r[i].key<<" ";
}
return 0;
}
有个小疑问,为什么完全二叉树的最后一个非终端结点是N/2