为了查找方便,通常希望计算机中的表是按关键字有序的。排序就是重新排列表中的元素,使表中的元素满足按关键字有序的过程,排序(以非递减序列举例)的确切定义如下:

输入:n个记录R1,R2,…,Rn,对应的关键字为k1,k2,…,kn

输出:输入序列的一个重排R1,R'2,…,R'n,使得K'1<=K'2<=…<=K'n

算法的稳定性。稳定性主要是对算法性质的描述,若待排序表中有两个元素Ri和Rj,其对应的关键字相同,即keyi=keyj,且在排序前Ri在Rj前面,若使用某一排序算法排序后,Ri仍在Rj的前面,则称这个排序算法是稳定的,否则称算法是不稳定的。

在排序过程中,根据数据元素是否完全在内存中,又可将排序算法分为两类:内部排序和外部排序;内部排序指在排序期间元素全部存放在内存中的排序,外部排序则是指在排序过程中元素无法全部同时存放在内存中,必须在排序过程中,根据根据要求不断地在内、外存之间移动的排序。

一般情况下,内部排序算法在执行过程中都要进行两种操作:比较和移动。当然,并非所有的内部排序算法都要基于比较操作,像基数排序就不基于比较。通常可以将排序算法分为五类:插入排序、交换排序、选择排序、归并排序和基数排序五大类。

1.插入排序

插入排序的基本思想是每次将一个待排序的记录按其关键字大小插入前面已排好序的子序列,直到全部记录插入完成。

1.1直接插入排序

直接插入排序是指,对序列A的n个元素A[0]~A[n-1],令i从i=1开始枚举(A[0]就一个元素已经有序),进行n-1趟操作,第i趟都从[0, i-1]中找一个位置将A[i]插入,使得[0, i]范围有序。具体做法一般是从后往前枚举已有序的部分来确定插入位置。

void insertSort(void *A, int n){
for(int i = 1; i < n; i++){ //从i=1开始,进行n-1趟排序
int temp = A[i], j = i;
while(A[j] < A[j-1] && j > 0){ //j>0防止A[j-1]数组越界
temp = A[j-1];
A[j-1] = A[j];
A[j] = temp;
j--;
}//将A[i]插入前i个有序队列中
}
}

直接插入排序的性能分析:

空间复杂度:算法原地工作,所需辅助空间为常数,因此空间复杂度为:  O(1);

时间复杂度:在最好的情况下,表中元素已经有序,只需要比较n-1次,因此时间复杂度为O(n);

在最坏的情况下,表中元素顺序与排序结果正好相反,需要(1+2+……+n-1)次比较,同理,每比较一次就需要交换一次,需要(n-1)*n/2次,因此时间复杂度此时为O(n2)。

1.2折半插入排序

在直接插入排序中,总是边比较边移动元素;对于折半插入排序,则是将比较和移动元素的操作分开。先折半查找出元素的待插入位置,然后统一地移动待插入位置后的所有元素。

void insertSort(void *A, int n){
int temp, i, j, mid, begin, end;
for(i = 1; i < n; i++){
temp = A[i];
begin = 0, end = i - 1;
while(begin <= end){ //折半查找
mid = (begin + end) / 2; //折半
if(A[mid] > temp) end = mid - 1; //查左半表
else begin = mid + 1; //查右半表
}
for(j = i - 1; j >= end + 1; j--) //统一移动
A[j + 1] = A[j];
A[end + 1] = temp;
}
}

折半插入排序的性能分析:

空间复杂度:算法原地工作,所需辅助空间为常数,因此空间复杂度为:  O(1);

时间复杂度:可以看出折半插入排序仅减少了比较的次数,约为O(nlog2n),而移动的次数未变,仍未O(n2)。

1.3希尔排序

根据直接插入排序的时间复杂度分析,我们可以知道在对于基本有序且数据量不大的排序表更适合。希尔排序就是针对这两点对直接插入排序改进而来,又成缩小增量排序。

希尔排序的基本思想是,先将待排序表分割成若干个形如L[i, i+d, i+2d, i+3d,…,i+kd]的特殊子表,以某个增量d的记录组成子表,对各个子表分别进行直接插入排序,当整个表中元素已基本有序时,再对全体记录进行一次直接插入排序。

希尔排序的过程如下:先取一个小于n的步长d1(d1小于n),将若有距离为d1倍数的记录放在同一组,记为d1组,在组内进行直接插入排序;然后再取第二个步长d2(d2小于d1),重复第一步,直到取得dt=1,此时即所有记录已放在同一组,再进行直接插入排序,此时因为局部有序性,能够很快得到结果。

void ShellSort(void A[], int n){
int temp, i, j, d;
for(d = n / 2; d >= 1; d /= 2){ //步长d的设置
for(i = d; i < n; i++){
if(A[i] < A[i - d]){
temp = A[i];
for(j = i - d; j > 0 && temp < A[j]; j -= d)
A[j + d] = A[j]; //记录后移,查找插入位置
A[j + d] = temp;
}
}
}
}

希尔排序的性能分析:

空间复杂度:算法原地工作,所需辅助空间为常数,因此空间复杂度为:  O(1);

时间复杂度:涉及数学上仍未解决的难题,n在某个特定的范围内,最好的情况下时间复杂度为O(n1.3),最坏情况下的时间复杂度为O(n2)。