今天发现其实我不是很会堆排序,重新整理记录一下。
堆排序:
将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的n-1个元素重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列,这个过程叫堆排序。
围绕两个问题:
1.如何建堆
建堆是一个从下往上进行“筛选”的过程
2.如何调整元素在堆中的位置
第二个问题解决方法——筛选 (以堆中最大元素“沉降”)
输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。
建堆代码:
template<typename E,typename Comp>
void heapsort(E A[],int n) {//heapsort
E maxval;
maxheap<E,Comp> H(A,n,n);
for(int i=0;i<n;i++) { //now sort
maxval=H.removefirst(); //put max at end
}
}
建堆图示:
堆排序
主要想强调一下代码。
template <typename E,typename Comp>
void mergesort() {
if(left==right) return; //终止条件为区间大小为1
int mid=(left+right)/2;
mergesort<E,Comp>(A,temp,left,mid); //直接以mid为界递归
mergesort<E,Comp>(A,temp,mid+1,right);
for(it i=left;i<right;i++) //把排好序的左右两部分copy到temp
temp[i]=A[i];
//借助temp将左右两端排好序,放入A
int i1=left;int i2=mid+1;
for(int curr=left;curr<=right;curr++) {
if(i1=mid+1) A[curr]=temp[i2++];
else if(i2>right) A[curr]=temp[i1++];
else if(Comp::prior(temp[i1],temp[i2]))
A[curr]=temp[i1++];
else A[curr]=temp[i2++];
}
}
分配排序
(只能对0到n-1个元素进行排序)
(可以适用于外排)
动机:按关键码划分,不进行关键码的比较
基本思想:根据关键码的位置确定一个记录在排序中的最终位置。
template <typename E,class getKey>
void binsort(E A[],int n) {
List<E> B[MaxKeyValue];
E item;
for(int i=0;i<n;i++) B[A[i]].append(getKey(A[i]));
//在B的第A[i]个桶中放入A[i]
for(int i=0;i<MaxKeyValue;i++)
for(B[i].setStart();B[i].getValue(item);B[i].next())
output(item);
//检查每个盒子,输出,可能用了迭代器,这里看不懂
}
桶式排序(和分配排序有相似之处)
因为分配时必须检查每个盒子中是否已有记录。
可以观察代码发现时间代价为O(n+MaxKeyValue)
基数排序:(是一种分配排序)
基本过程如图,第一趟对个位进行排序,按照个位数字丢入十个盒子中。
然后从里到外,从上到下把元素取下来。形成第一个序列。
在第二次排序中,按照十位的十个数字,将元素丢入十个盒子中。
然后从里到外,从上到下把元素取出来,得到第二个序列。本题中为最终的目标序列。
实现过程:
代码:
//不得不说基数排序的代码非常巧妙,点个赞
template <typename E,class getKey>
void radix(E A[],E B[],int n,int k,int r,int cnt[]) {
//cnt是辅助数组
int j;
for(int i=0,roti=1;i<k;i++,roti*=r) { //总共要循环n位
for(j=0;j<r;j++) cnt[j]=0; //初始化
for(j=0;j<n;j++) cnt[(getKey::key(A[j])/roti)%r]++;
//统计0~9号格子里各要扔多少元素
for(j=1;j<r;j++)
cnt[j]=cnt[j-1]+cnt[j];
//分别存储所有格子垒起来时,每个格子的下标位置
for(j=n-1;j>=0;j--) B[--cnt[getKey::key(A[j])/rtok%r]]=A[j];
//从最后一个开始,把元素从后向前的放入每个格子
for(j=0;j<n;j++) A[j]=B[j];
//一次循环结束
}
}
综上,基数排序时有最好渐进复杂性的排序算法。astonishing
总结一下所有排序算法性能
时间性能
1.平均时间性能
时间复杂度位O(nlogn): 快速排序、堆排序和归并排序
时间复杂度为O(n2): 直接插入排序、冒泡排序和简单选择排序
时间复杂度O(n): 基数排序
2.当待排序记录列按关键字有序时
直接插入排序和冒泡排序能达到O(n)时间复杂度
快速排序的时间性能退化为O(n2) ,最能展现快速排序优势情况为,每次pivot的最终位置都正好位于字段的最终间
3.简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变
空间性能
指排序过程中所需辅助空间的大小
1.所有的简单排序方法(直接插入、冒泡、简单选择)和堆排序方法的空间复杂度O(1),这里可以说道说道,堆排序算法的顺序实现中,传入装有n个元素的数组,直接在其上进行时间复杂度为O(nlogn)的建堆和后续恢复就可以。所以空间复杂度认为是O(1).
2.快速排序为O(logn),为递归程序执行过程中,栈所需的辅助空间
3.归并排序所需辅助空间最多,复杂度为O(n)
本章讨论的各种排序方法,除基数排序外,其他方法都是基于“比较关键字”进行排序的排序方法。可以证明,这类排序法所能达到的最快时间复杂度为O(nlogn) 基数排序不在这个范畴内,所以不受限制
稳定与否
1.稳定的排序算法
插入排序、冒泡排序和归并排序、分配排序和基数排序
2.不稳定的排序算法
选择排序,堆排序、快速排序和shell排序