排序
基本内容是:
排序的基本概念
插入排序
交换排序
选择排序
归并排序
分配排序
排序:给定一组记录的集合{r1, r2, ……, rn},其相应的关键码分别为{k1, k2, ……, kn},排序是将这些记录排列成顺序为{rs1, rs2, ……, rsn}的一个序列,使得相应的关键码满足ks1≤ks2≤……≤ksn(称为升序)或ks1≥ks2≥……≥ksn(称为降序)。
正序:待排序序列中的记录已按关键码排好序。
逆序(反序):待排序序列中记录的排列顺序与排好序的顺序正好相反。
趟:在排序过程中,将待排序的记录序列扫描一遍称为一趟。
通常,一次排序过程需要进行多趟扫描才能完成
排序算法的稳定性:
假定在待排序的记录集中,存在多个具有相同键值的记录,
若经过排序,这些记录的相对次序仍然保持不变,
即在原序列中,ki=kj且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
排序算法的性能
时间复杂性:基本操作。
内排序在排序过程中的基本操作:
⑴比较:关键码之间的比较;
⑵移动:记录从一个位置移动到另一个位置。
空间复杂性: 辅助存储空间。
辅助存储空间是指在数据规模一定的条件下,除了存放待排序记录占用的存储空间之外,执行算法所需要的其他存储空间。
排序的分类
根据排序数据在内存中还是在外存中:
内排序:在排序的整个过程中,待排序的所有记录全部被放置在内存中
外排序:由于待排序的记录个数太多,不能同时放置在内存,而需要将一部分记录放置在内存,另一部分记录放置在外存上,整个排序过程需要在内外存之间多次交换数据才能得到排序的结果。
根据排序过程中所进行的基本操作分:
基于比较:基本操作——关键码的比较和记录的移动,其最差时间下限已经被证明为O(nlog2n)。
不基于比较:根据关键码的分布特征。比如,桶式排序,基数排序(多关键字排序)
根据排序过程中所进行的基本操作分:
基于比较:基本操作——关键码的比较和记录的移动,其最差时间下限已经被证明为O(nlog2n)。
不基于比较:根据关键码的分布特征。比如,桶式排序,基数排序(多关键字排序)
插入排序
插入排序的主要操作是插入,
其基本思想是:每次将一个待排序的记录按其关键码的大小插入到一个已经排好序的有序序列中,直到全部记录排好序为止。
插入类排序方法有以下两种:
直接插入排序
希尔排序
直接插入排序
void insertSort (int r[ ], int n){
for (i=2; i<=n; i++) {
r[0]=r[i]; j=i-1;
while (r[0]<r[j]) {
r[j+1]=r[j];
j=j-1;
}
r[j+1]=r[0];
}
}
时间复杂度为O(n2)
空间性能:需要一个记录的辅助空间。
直接插入排序算法是一种稳定的排序算法。
直接插入排序算法简单、容易实现,适用于待排序记录基本有序或待排序记录个数较小的情况。
当待排序的记录个数较多时,大量的比较和移动操作使直接插入排序算法的效率降低。
希尔排序
直接插入排序算法简单、容易实现,适用于待排序记录基本有序或待排序记录个数较小的情况。
当待排序的记录个数较多时,大量的比较和移动操作使直接插入排序算法的效率降低。
基本思想:
将整个待排序记录分割成若干个子序列,
在子序列内分别进行直接插入排序,
待整个序列中的记录基本有序时,对全体记录进行直接插入排序。
void Shellsort(int r[],int n){
for (d=n/2; d>=1; d=d/2){
for (i=d+1; i<=n; i++) {
r[0]=r[i];
j=i-d;
while (j>0 && r[0]<r[j])
{
r[j+d]=r[j];
j=j-d;
}
r[j+d]=r[0];
}
}
}
希尔排序的时间性能在O(n2)和O(nlog2n)之间。
交换排序
交换排序的主要操作是交换,其主要思想是:在待排序列中选两个记录,将它们的关键码相比较,如果反序(即排列顺序与排序后的次序正好相反),则交换它们的存储位置。
起泡排序
基本思想:两两比较相邻记录的关键码,如果反序则交换,直到没有反序的记录为止。
void BubbleSort(int r[ ], int n)
{
exchange=n;
while (exchange)
{
bound=exchange;
exchange=0;
for (j=1; j<bound; j++)
if (r[j]>r[j+1]) {
r[j]←→r[j+1];
exchange=j;
}
}
}
平均情况:时间复杂度为O(n2)。
快速排序
首先选一个轴值(即比较的基准),
通过一趟排序将待排序记录分割成独立的两部分,
前一部分记录的关键码均小于或等于轴值,
后一部分记录的关键码均大于或等于轴值,
然后分别对这两部分重复上述方法,直到整个序列有序。
选择轴值的方法:
1.使用第一个记录的关键码;
2.选取序列中间记录的关键码;
3.比较序列中第一个记录、最后一个记录和中间记录的关键码,取关键码居中的作为轴值并调换到第一个记录的位置;
4.随机选取轴值。
选取不同轴值的后果:
决定两个子序列的长度,子序列的长度最好相等。
一次划分
int Partition(int r[ ], int first, int end)
{
i=first; j=end; //初始化
r[0]=r[i];
while (i<j)
{
while (i<j && r[0]<= r[j]) j--; //右侧扫描
if (i<j) {
r[i]=r[j]; i++; //将较小记录交换到前面
}
while (i<j && r[i]<= r[0]) i++; //左侧扫描
if (i<j) {
r[j]=r[i]; j--; //将较大记录交换到后面
}
}
r[i]=r[0];
retutn i; //i为轴值记录的最终位置
}
void QuickS
ort (int r[ ], int first, int end )
{//在序列 first~end中递归地进行快速排序
if (first < end) {
pivotpos = Partition (r, first, end );
QuickSort (r, first, pivotpos-1);
QuickSort (r, pivotpos+1, end );
}
}
选择排序
选择排序的主要操作是选择,其
主要思想是:每趟排序在当前待排序序列中选出关键码最小的记录,添加到有序序列中。
简单选择排序
基本思想:第i 趟在n-i+1(i=1,2,…,n-1)个记录中选取关键码最小的记录作为有序序列中的第i个记录。
void selectSort ( int r[ ], int n)
{
for ( i=1; i<n; i++)
{
index=i;
for (j=i+1; j<=n; j++)
if (r[j]<r[index]) index=j;
if (index!=i) r[i]<==>r[index];
}
}
简单选择排序
基本思想:第i 趟在n-i+1(i=1,2,…,n-1)个记录中选取关键码最小的记录作为有序序列中的第i个记录。
堆排序
首先将待排序的记录序列构造成一个堆(大顶堆),
此时,选出了堆中所有记录的最大者,然后将它从堆中移走,
将剩余的记录再调整成堆,
这样又找出了次大的记录,以此类推,直到堆中只有一个记录。
void HeapSort ( int r[], int n)
{
for (i=n/2; i>=1; i--)
sift(r, i, n) ;
for (i=1; i>n; i++ )
{
r[1]←→r[n-i+1];
sift(r, 1, n-i);
}
}
建堆:O(n)
删除堆顶的调整:O(log2n)
一次建堆 ,n次删除堆顶
总时间代价为O(nlog n)
空间代价为O(1)
归并排序
归并:将两个或两个以上的有序序列合并成一个有序序列的过程。
归并排序的主要操作是归并,其主要思想是:将若干有序序列逐步归并,最终得到一个有序序列。
二路归并排序
基本思想:
将一个具有n个待排序记录的序列看成是n个长度为1的有序序列,
然后进行两两归并,
得到n/2个长度为2的有序序列,
再进行两两归并,得到n/4个长度为4的有序序列,
……,
直至得到一个长度为n的有序序列为止。
void MergePass (int r[ ], int r1[ ], int n, int h)
{
i=1; //第一个子序列的第一个元素
while (i≤n-2h+1) //情况1
{
Merge (r, r1, i, i+h-1, i+2*h-1);
i+=2*h;
}
if (i<n-h+1) Merge (r, r1, i, i+h-1, n); //情况2
else for (k=i; k<=n; k++) //情况3
r1[k]=r[k];
}
void MergeSort (int r[ ], int r1[ ], int n )
{
h=1;
while (h<n)
{
MergePass (r, r1, n, h);
h=2*h;
MergePass (r1, r, n, h);
h=2*h;
}
}
void msort(int a[], int r[], int s, int t){
if(s==t)
return; //如果只有一个数字则返回,无须排序
int mid=(s+t)/2;
msort(s,mid); //分解左序列
msort(mid+1,t); //分解右序列
int i=s, j=mid+1, k=s; //接下来合并
while(i<=mid && j<=t) {
if(a[i]<=a[j]) {
r[k]=a[i]; k++; i++;
}
else {
r[k]=a[j]; k++; j++;
}
}
while(i<=mid) { //复制左边子序列剩余
r[k]=a[i]; k++; i++;
}
while(j<=t) { //复制右边子序列剩余
r[k]=a[j]; k++; j++;
}
for(int i=s; i<=t; i++)
a[i]=r[i];
return 0;
}
各种排序算法比较
稳定性
所有排序方法可分为两类,
(1)一类是稳定的,包括直接插入排序、起泡排序、和归并排序,基数桶式排序;
(2)另一类是不稳定的,包括直接选择排序、希尔排序、快速排序和堆排序。
简单性
(1)一类是简单算法,包括直接插入排序、直接选择排序和起泡排序,
(2)另一类是改进后的算法,包括希尔排序、堆排序、快速排序、归并排序和基数排序,这些算法都很复杂。