插入排序
插入排序基本思想:每次将一个待排序的记录按其关键字大小插入前面已经排好序的子序列,直到全部记录插入完成。
三个重要的排序算法:直接插入排序,折半插入排序,希尔排序
直接插入排序
有序序列L[1…i-1] | L(i) | 无序序列L[i+1…n] |
---|
其中L()表示元素 L[]表示表
将元素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次就能得到一个有序的表。插入排序实现上通常认为采用就地排序(空间复杂度为O(1),因而在从后往前的比较中,需要反复把已排好的元素逐步向后挪位,为新元素提供插入空间。
直接排序无哨兵代码:
//直接插入排序
void InsertSort(int A[],int n ){
int temp,j;
for(int i =1;i<n;i++){
if(A[i]<A[i-1]){ //刚开使得时候使A[1]和A[0]比较,如果A[0]>A[1]
temp=A[i];//将A[1]的值记录下来
for(j=i-1;j>=0&&A[j]>temp;--j){//将前面的值进行比较找最终位置
A[j+1] = A[j];//将大于指定元素值的元素向后移动
}
A[j+1] = temp;//找到最终位置,赋值
}
}
}
直接排序有哨兵:
void InsertSort(int A[] , int n ){
int i ,j;
for(i=2;i<=n;i++){//依次将L[2]~A[n]插入前面已经排好序的序列
if(A[i]<A[i-1]){//若A[i]的关键码小于其前驱,将A[i]插入有序表
A[0] = A[i];//复制为哨兵,A[0]不存放元素
for(j=i-1;A[0]<A[j];--j){//从后往前查找待插入位置
A[j+1] = A[j];//向后挪位
}
A[j+1] = A[0];//复制到插入位置
}
}
}
假定初始序列为49 ,38,65,97,76,13,27,49* ,初始时49可以视为一个已排好序的子序列。
直接插入排序示例
初始关键字 | 哨兵L.R[0] | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 * |
---|---|---|---|---|---|---|---|---|---|
i=2 | 38 | 38 | 49 | ||||||
i=3 | 38 | 38 | 49 | 65 | |||||
i=4 | 38 | 38 | 49 | 65 | 97 | ||||
i=5 | 76 | 38 | 49 | 65 | 76 | 97 | |||
i=6 | 13 | 13 | 38 | 49 | 65 | 76 | 97 | ||
i=7 | 27 | 13 | 27 | 38 | 49 | 65 | 76 | 97 | |
i=8 | 49* | 13 | 27 | 38 | 49 | 49* | 65 | 76 | 97 |
直接插入排序算法的性能分析:
空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)
时间效率:在排序过程中,有序子表中逐个地插入元素的操作进行了n-1趟,每趟从左分为比较关键字和移动元素,而比较次数和移动次数取决于待排序表的初始状态。
在最好的情况下,表中元素已经有序,此时每插入一个元素,都只需要比较一次而不用移动元素,因而时间复杂度为O(n)
直接插入排序算法的时间复杂度为O(n^2)
稳定性:由于每次都是先比较后移动,所以不会出现相同元素相对位置发生变化的情况,所以直接插入排序是一个稳定的排序方法。
适用性:直接插入排序适用于顺序存储和链式存储的线性表,可以从前往后查找指定元素的位置。
折半插入排序
折半插入排序,即边折半查找出元素的待插入位置,然后统一地移动待插入位置之后的元素。
- 当low > high时,折半查找停止,应将[low ,i-1]内的元素全部后移,并将A[0]复制到low所指的位置
- 当A[mid] == A[0]时,为保证稳定性 应继续在mid所指的位置右边寻找插入位置
void InserSort(int A[] ,int n ){
int i ,j , low, high;
for(i=2;i<=n;i++){//依次将A[2]~A[n]插入前面的已排序序列
A[0] = A[i];//将A[i]暂存到A[0]
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[j+1] = A[0];//插入操作
}
}
折半插入排序减少了比较元素的次数,约为O(nlog2n),该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n,而元素的移动次数并没有改变,它依赖于排序的初始状态,因此折半插入排序的时间复杂度仍然是O(n^2),但对数据量不是很大的排序表,折半插入排序往往能表现出很好的性能,排序算法是一种稳定的排序算法。
希尔排序
希尔排序的基本思想:先将待插入排序表分割成若干形如L[i,i+d,i+2d,…,i+kd]的特殊子表,即把相隔某个增量的记录组成一个子表,对各个子表进行直接插入排序,当整个表中的元素已呈现基本有序是,再对全体记录进行一次直接插入排序。即先追求部分有序,再逐渐逼近全局有序。
希尔排序的过程:先取一个小于n的步长d1,把表中的全部记录分为d1组,所有距离为d1的倍数的记录放在同一组,再各组内进行直接插入排序,然后取第二个步长d2<d1,重复上述过程,直到dt=1,即所有记录已经放在同一组中,在进行直接插入排序,由于此时已经具有较好的局部有序性,故可以得到最终结果,
希尔排序算法的代码如下
void InsertSort(int A[] , int n ){
//A[0]只是暂存单元不是哨兵,当j<=0时,插入位置已到
int dk ,j ,i;
for(dk = n/2;dk>=1;dk=dk/2){//分组
for(i = dk+1;i<=n;++i){
if(A[i]<A[i-dk]){//需将A[i]插入有序增量子表
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)
稳定性:当关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,因此希尔排序是一种不稳定的排序方法。
适用性:希尔排序算法仅适用于线性表为顺序存储的情况。