选择排序的思想:每一趟(如第i趟)在后面的n-i+1(i=1,2,3…n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n-1趟做完,待排序元素就剩下1个,就不用再选了。
选择排序主要有两种:
1.简单选择排序
简单选择排序每趟选取最小(最大)的一个元素放在有序子序列中,因此,n个元素的数组需要n-1趟排序才能使整个排序表有序。
void SelectSort(int *a,int len){
for(int i=0;i<len-1;i++){
int minindex=i;
for(int j=i+1;j<len;j++){
if(a[j]<a[minindex])
minindex=j;
}
if(minindex!=i){
int temp=a[i];
a[i]=a[minindex];
a[minindex]=temp;
}
}
}
性能分析
空间效率:O(1)
时间效率:简单选择排序中关键字比较次数与序列初始状态无关,始终是n(n-1)/2,时间复杂度始终是O(n^2)
稳定性:不稳定,如 {2,2 *,1},排序后{1,2 *,2}
2.堆排序
堆的定义:n个关键字的序列L[1…n],当且仅当满足下列条件时:
1)满足 L[i] >= L[2i] 且L[i]>=L[2i+1] ,称之为大根堆,或
2)满足 L[i] <= L[2i] 且L[i]<=L[2i+1] ,称之为小根堆。
堆的定义可以将其看做是一颗完全二叉树,则当下标为i的结点的值均大于其左孩子(下标为2i)及右孩子(2i+1)时,称为大根堆,反之为小根堆。
注:由于堆排序中需要比较下标为 i 结点与其左右孩子结点的值,因此我们习惯在堆排序时将数组定义为L[1…n],空出L[0]位置,因此其中下标为i的结点的左右孩子下标正好是2i和2i+1。另外,下标为 i 的结点,其双亲结点的下标为i/2向下取整
堆排序的思想(以大根堆为例,采用大根堆的堆排序结果为升序,小根堆结果为逆序):
1.首先将L[1…n]的n个元素建立为大根堆,不难看出,在大根堆中,最大的元素在堆顶位置
2.输出堆顶元素即为最大值放在表尾,并将该元素从堆中删除
3.将去掉堆顶后剩下的元素重新调整为堆,重复以上,直到剩下最后一个元素为止。
#include<iostream>
#include<stdlib.h>
using namespace std;
//从下标为k的结点开始向下调整,使之成为大根堆
void HeadAdjust(int *a,int k,int len){
a[0]=a[k];//将该元素保存在a[0],防止覆盖
for(int i=2*k;i<=len;i*=2){
if(i<len&&a[i]<a[i+1])
i++; //比较左右孩子结点的大小,让i指向左右孩子中较大的一个
if(a[0]>a[i]) //如果该元素已经大于左右孩子中较大的时,表明以该节点为根的堆已经为大根堆
break;
else{
a[k]=a[i];
k=i;//接着向下调整
}
}
a[k]=a[0];
}
//建立初试大根堆
//建堆时,对于L【1..n】构成的完全二叉树,其下标n/2的元素正好为第一个左右孩子不为空的结点,
//我们只需从n/2开始向前依次调整
void BuildMaxHeap(int *a,int len){
for(int i=len/2;i>0;i--){
HeadAdjust(a,i,len);
}
}
//堆排序
//第一步:建立初始大根堆;
//第二步:交换堆顶与表尾元素(即删除堆顶元素),再接着进行n-1趟向下调整输出
void HeapSort(int *a,int len){
BuildMaxHeap(a,len);//建立大根堆
for(int i=len;i>1;i--){
int temp=a[i];//将建立好的大根堆的堆顶元素(最大元素)移到表尾,与表尾交换
a[i]=a[1];
a[1]=temp;
HeadAdjust(a,1,i-1);//再将剩下的元素重新调整为堆
}
}
int main(){
int a[]={0,3,1,4,6,9,5412,4,5,72,3,512,35,7421,3,5,42,2,44,1,23,54,1,2,66,523,565,2};
a[0]=0;//将第一个位置空下来
int len=sizeof(a)/sizeof(int)-1;//计算数组长度
for(int i=1;i<=len;i++){
cout<<a[i]<<" ";
}
cout<<endl;
HeapSort(a,len);//堆排序
for(int i=1;i<=len;i++){
cout<<a[i]<<" ";
}
return 0;
}
性能分析
空间效率:O(1)
时间效率:建立初始堆时间为O(n),之后有n-1趟向下调整操作,每次调整的时间复杂度为O(h)=O(log2n)(h表示n个元素列表构成的完全二叉树的高度),故在最好、最坏、平均情况下,堆排序时间复杂度为建堆时间+向下调整时间O(n)+O((n-1)*h)=O(n)+O((n-1)*log2n)=O(nlog2n)
稳定性:不稳定
堆排序适合关键字较多的情况。如从1亿个数中选取前100个最大值,首先使用一个大小为100的数组,读入前100个数,建立小根堆,而后依次读入剩下的数,若小于堆顶元素,则舍弃,若大于堆顶,则用该数替代堆顶并重新调整堆,带数据读取完毕,堆中的100个数即为所求。