1. 直接插入排序
一种最直观,最简单的排序方式。假设在排序过程中,待排序表L[1…….n]在某次排序过程中的某一时刻状态如下:
有序序列L[1…..i-1] | L(i) | 无序序列L[i+1…….n] |
---|
a.操作步骤如下:
1) 查找出L[i]在有序序列L[1…….i-1]中的插入位置k(注意,L[i]已经赋值在哨兵位置)
2)将L[k…….i-1]所有元素全部后移一个位址
3)将L[i]复制到L[k].
b.代码
void InsertSort(ElemType A[], int n){
int i,j;
for(i=2;i<=n;i++) //依次将A[2].....A[n]插入到前面已排序序序列
if(A[i].key<A[i-1].key){ //A[i]的关键码小于前值,则需要排序插入前面
A[0] = A[i]; //“哨兵”
for(j=i-1;A[0].key<A[j].key;--j) //从后向前插
A[j+1] = A[j]; //向后挪位
A[j+1] = A[0]; //复制到插入位置
}
}
c.性能分析
空间复杂度O(1). 时间复杂度O(n^2).
稳定性:每次插入元素总是从后面开始比较,不会出现相同元素相对位置发生变化的情况。是稳定的排序方法。
2. 折半插入排序
表与上面的表相同,故不添上去了。
a. 基本思想
上述代码中是边比较边移动元素。而折半查找不同,它是在已排好序的前表找到待插入位置(使用到折半查找),再统一移动待插入位置之后的元素。
b. 代码
void InsertSort(ElemType A[], int n){
int i,j,low,high,mid; //折半查找时使用的变量出现了
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].key>A[0].key) 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]; //插入
}
}
c. 性能分析
空间复杂度O(1). 时间复杂度O(n^2)。减少了比较次数O(nlogn),而且比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n。移动次数未改O(n^2)。所以总体来说就是O(n^2)。
是个稳定的排序方法。
3. 希尔排序
a. 基本思想
直接额插入排序适用于基本有序和数据量不大的排序表。希尔排序是在直接插入排序的基础上改进,先将待排序表分割成若干个形如L[i,i+d,i+2d,……,i+kd]的“特殊”子表,分别进行直接插入排序。待基本有序后,再对全体记录进行一次直接插入排序。
实现步骤:先取一个小于n的步长d1,把表中全部记录分为d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组中进行直接插入排序,然后取第二个步长d2
b. 代码
void ShellSort(ElemType A[], int n){
//对顺序表作希尔插入排序。与直接插入排序相比,作了2处修改
//1. 前后记录位置的增量是dk,不是1。 2. r[0]只是暂存单元,不是“哨兵”
for(dk=n/2;dk>=1;dk=dk/2) //步长变化
for(i=dk+1;i<=n;++i)
if(A[i].key<A[i-dk].key){ //需将A[i]插入有序增量子表
A[0]=A[i]; //暂存在A[0]
for(j=i-dk;j>0&&A[0].key<A[j].key;j-=dk)
A[j+dk] = A[j]; //记录后移,查找插入的位置
A[j+dk] = A[0]; //插入
}//if
}
c. 性能评估
空间复杂度易从代码看出O(1)
时间复杂度O(n^2)
稳定性:不稳定。例:[3, #2, 2] -> [2, #2, 3] 其中2的相对位置发生了改变。
仅适用于当线性表为顺序存储的情况。