一. 归并排序
【基本思想】 将两个或两个以上的有序表合成一个新的有序表。
【归并-算法步骤】设有两段有序表A[ low …mid] A[mid+1…high] 存放在同一顺序表的相邻位置上,现将他们复制到辅助数组B中(这里是关键,决定了无论何种情况,即和输入状态无关,该算法空间复杂度为O(n))。每次从对应B中两个段取出一个记录进行关键字比较,将较小者放入A中,当数组B中有一段(逻辑上分为两段嘛)的下标超出其对应长度时(即该段的所有元素已经完全复制到A中,见代码),将另一段中剩余部分直接复制到A中。
【归并代码】
ElemType*B=(ElemType*)malloc((n+1)*sizeof(ElemType)); //辅助数组
Void Merge(ElemTypeA[],int low,int high)
{
For( k=low;k<=high;k++)
B[k]=A[k];
For(i=low;j=mid+1;k=i;i<=mid&&j<=high;k++) //注意k起点是i
{
If(B[i]<=B[j])
A[k]=B[i++]; //对暂存表B扫描比较,再写回原表A中
Else
A[k]=B[j++];
}
While(i<=mid) A[k++]=B[i++]; //第一个表未检测完
While(j<=high) A[K++]=B[j++]; //【注】只会执行一个
}
【2路归并排序-递归形式】
1. 分解:将含有n个元素的待排序表分成各含n/2个元素的子表,采用2路归并排序算法对两个子表递归的进行的排序。 【分治的思想,太巧妙了】
2. 合并:合并两个已排序的子表得到排序结果
【代码】
VoidmergeSort(ElemType A[],int low,int high)
{
If(low<high) //只能low<high,如果low=high说明定位到一个元素了,无法归并
{
Int mid=(low+high)/2;
MergeSort(A,low,mid); //注意mid, 归并要保证所有元素都取到
MergeSort(A,mid+1,high);
Merge(A,low,mid,high); //处理完了子序表,就是归并了
}
}
【2路归并排序-递归形式】
1. 分解:将含有n个元素的待排序表分成各含n/2个元素的子表,采用2路归并排序算法对两个子表递归的进行的排序。 【分治的思想,太巧妙了】
2. 合并:合并两个已排序的子表得到排序结果
【代码】
VoidmergeSort(ElemType A[],int low,int high)
{
If(low<high) //只能low<high,如果low=high说明定位到一个元素了,无法归并
{
Int mid=(low+high)/2;
MergeSort(A,low,mid); //注意mid, 归并要保证所有元素都取到
MergeSort(A,mid+1,high);
Merge(A,low,mid,high); //处理完了子序表,就是归并了
}
}
二. 基数排序
【思想】不是基于比较进行排序,而是采用多关键字排序思想(即基于关键字各位的大小进行排序的),借助”分配“和“收集”两种操作对单逻辑关键字进行排序。分为最高位有先(MSD)和最低位优先(LSD=Least significant digit)排序
【性能分析】
空间效率:一趟排序需要辅助存储空间为2r(分别是r个队头和队尾指针,r是基数),但以后的排序中重复使用这些队列,所以基数排序的空间复杂度为O(2r)
时间效率:基数排序需要进行d(表示位数)趟分配和收集,一趟分配需要O(n)(扫描表并逐个分配到对应的队列中去,),一趟收集需要O(r)(扫描r个队列,修改一次非空队列的尾指针即可),所以基数排序时间复杂度为O(d(n+r)),它与序列的初始状态无关
各算法的比较
1. 时间复杂度:
1) 简单选择排序,直接插入排序和冒泡排序的平均时间复杂度都为O( ,并且实现过程也比较简单, 但直接插入排序和冒泡排序最好情况下时间复杂度可达到O(n),而简单选择排序则与序列的初始状态无关。
2) 希尔排序作为插入排序的拓展,对较大规模的排序都可达到很高效率,但是目前未得出其精确的渐进时间。
3) 堆排序是利用一种称为堆的数据结构,可以在线性时间内完成建堆,并且在O( ) 内完成排序过程。
4) 快速排序是基于分治思想,虽然最坏情况下时间复杂度达到O( ,但其平均性能可以达到O( ),在实际应用中常常优于其他算法。
5) 归并排序同样是基于分治思想,但由于其分割子序列和初始状态排列无关,因此它的最好,最坏和平均时间复杂度都为O( )
1. 空间复杂度:
1) 简单选择排序,插入排序,冒泡排序,希尔排序和堆排序都仅需要借助常数个辅助空间。
2) 快速排序在空间上只使用一个小的辅助栈,用于实现递归,平均情况下为O( )(因为最坏情况为O(n))。
3) 二路归并排序在合并操作中需要借助O(n)辅助空间用于元素复制,虽然有其方法可以克服这个缺点,但是代价是算法复杂和时间复杂度增加。
2. 稳定性: 插入排序,冒泡排序,归并排序和基数排序都是稳定的排序方法,而简单选择排序,快速排序,希尔排序和堆排序都是不稳定的排序方法。(原因在于这些有跨区间交换)
3. 过程特征:
1) 冒泡排序和堆排序每次循环后都能产生当前最大值或者最小值,
2) 快速排序一次循环就确定一个元素的最终位置。
排序算法小结
1. 若n较小(n<=50),则可以采用直接插入排序或简单选择排序。由于直接插入排序所需记录移动操作较简单选择排序多,因而当记录本身信息量较大时,用简单选择排序好。
2. 若文件的初始状态已按关键字基本有序,则选用直接插入或冒泡排序为宜(最好情况可以为O(n))
3. 若n较大,则应采用时间复杂度为O( )的排序方法:快速排序,堆排序或归并排序。 快速排序被认为是目前基本比较的内部排序法中最好的方法。
4. 在基于比较的排序算法中,每次比较两个关键字大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定的过程,由此可以证明:当文件的n个关键字随机分布时,任何借助于“比较”的排序算法,至少需要O( )(n次判定,每次至少需要)
5. 当记录本身信息量很大时,为避免耗费大量时间移动记录,可用链表作为存储结构。
一. 外部排序
【引】大文件进行排序时,由于无法将整个文件拷贝进内存中进行排序。因此,需将待排序的记录存储在外存上,排序时再把数据一部分一部分的调入内存进行排序。在排序过程中需要多次进行内外存之间的交换,对外存文件中的记录进行排序后仍然被放到原有文件中。
这种排序方法称之为外部排序。
【定义】由两个相对独立的阶段组成。
1. 按可用内存大小,将外存上含n个记录的文件分成若干长度为l的子文件或段(segment),依次读入内存并利用有效的内部排序方法对它们进行排序,并将排序后得到了有序子文件重新写回外存,通常称这些有序子文件为归并段或顺串(run)
对这些归并段进行逐趟归并,使归并段(有序的子文件)逐渐由小到大,直至得到整个
并将l个归并段归并成⌈l/m⌉个归并段,直到最后形成一个大的归并段为止。树的高度=⌈⌉=归并趟数S。可见,
增大归并路数,或者减少初始归并段个数,都能减少归并趟数S,以减少读写磁盘次数d,达到提高外部排序速度的目的。