排序的基本概念
排序:就是重新排列表中的元素,使得表中的元素满足按关键字递增或者递减的过程。
输入:n个记录
R1,R2,⋯,Rn
,对应的关键字为
k1,k2,⋯,kn
。
输出:输入的序列的一个重排
R′1,R′2,⋯,R′n
,使得
k′1≤k′2≤⋯k′n
算法的稳定性:如果待排序的表中有两个元素
Ri,Rj
,其对应的关键字
key1=key2
,且在排序前
Ri
在
Rj
前面,如果使用某一排序算法后,
Ri
仍然在
Rj
前面,则成这个排序算法是稳定的。否则成排序算法是不稳定的。需要注意的是,算法是否具有稳定性并不能衡量一个算法的优势。它主要是对算法的性质进行描述。
根据数据元素是否完全在在内存中,可将排序算法分为两类:内部排序是指在排序期间元素全部存放在内存中的排序; 外部排序是指在排序期间元素无法全部同时存放在内存中,必须在排序的过程中根据要求不断地在内,外存之间移动的排序。
插入排序
插入排序的基本思想在于每次将一个待排序的记录,按其关键字体的大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
直接插入排序
假如待排序序列
L[1⋯n]
在某次排序的过程中的某一时刻的状态如下:
(有序序列)
L[1⋯i−1]
L(i)
L[i+1⋯n]
(无序序列)
为了实现将元素
L(i)
插入到已有序的子序列
L[1⋯i−1]
中,需要执行以下操作:
查找出 L(i) 在 L[1⋯i−1] 中的插入位置 k 。
将
L[k⋯i−1] 中的所有元素全部后移一个位置。将 L(i) 复制到 L(k)
为了实现对 L[1⋯n] 的排序,可以将 L(2)∼L(n) 依次插入到前面已排好序的子序列中,初始假定 L[1] 是一个已经排好序的子序列。上述操作执行 n−1 次就能得到一个有序的表。
void InsertSort(int A[],int n)
{
int i,j;
for (i=2;i<=n;i++){ //依次将A[2]~A[n]插入到前面排好序序列
if(A[i] < A[i-1]){
A[0] = A[i];
for(j=i-1;A[0]<A[j];--j) //从后往前查找带插入位置
A[j+1] = A[j]; //向后挪位
A[j+1] = A[0];
}
}
}
空间效率:仅使用了常数个辅助单元,因而空间复杂度
O(1)
时间效率 : 最好的情况下,表中元素已经有序,此时每次插入都只需比较一次而不需要移动,因而时间复杂度为
O(n)
。最坏情况下,表中元素刚好逆序,总的比较次数达到最大
∑ni=2i
,总移动次数也达到最大,为
∑ni=2(i+1)
。平均情况下,待排序的元素是随机的,直接插入排序的时间复杂度为
O(n2)
稳定性:由于每次插入元素时总是从后向前先比较再移动,所以不会出现形相对位置发生变化的情况,所以直接插入排序是一个稳定的排序方法。
折半插入排序
在之前的直接插入排序的时候 L[1⋯i−1] 是有序的,在找插入位置的时候可以使用二分查找来优化这个查找的过程。于是对直接插入排序算法做如下改进。
void BinaryInsertSort(int A[],int n)
{
int i,j,low,mid,high;
for (i=2;i<=n;i++){ //依次将A[2]~A[n]插入到前面排好序序列
A[0] = A[i];
low = 1,high=i-1;
while(low<=high){
mid = (low+high)/2;
if(A[mid] > A[0]) high = mid - 1; //插找左半子表
else
low = mid+1; //查找右半子表
}
for(j=i-1;j>=high+1;--j)
A[j+1] = A[j];
A[high+1] = A[0];
}
}
折半插入排序只是减少了元素的比较次数,约为
O(nlogn)
,该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数
n
,时间复杂度仍然为
希尔排序
希尔排序的基本思想是:先将待排序表分割成若干个形如 L[i+1,i+d,i+2d,⋯i+kd] 的“特殊”子表,分别进行直接插入排序,当整个表中的元素呈“基本有序”时,再对全体记录进行一次直接插入排序。算法过程如下:
- 先取一个小于n的步长 d1 ,把表中全部记录分成 d1 个组,所有距离为 d1 的倍数的记录放在同一个组中,在各组进行直接插入排序。
- 选取第二个步长
d2<d1
,重复上述过程,直到所取到的
d1=1
,再进行直接插入排序。
代码如下:
void ShellSort(int A[],int n)
{
int dk,i,j;
for(dk=n/2;dk>=1;dk=dk/2){
for(i=dk+1;i<=n;i++){
if(A[i]<A[i-dk]){
A[0] = A[i];
for(j=i-dk;j>0&&A[0]<A[j];j-=dk)
A[j+dk] = A[j];
A[j+dk] = A[0];
}
}
}
}
空间效率:
O(1)
时间效率:当n在某个特定的范围内希尔排序的时间复杂度约为
O(n1.3)
,在最坏的情况下时间复杂度为
O(n2)
稳定性:当相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,因此希尔排序是不稳定的排序方法