插入排序是一种简单直观的排序算法,基本思想是:将一个待排序的元素按其关键字大小插入到前面已排好序的子序列,直至全部记录插入完成。今天要介绍的就是三钟插入排序,直接插入排序,折半插入排序,希尔排序。
一、直接插入排序:
插入排序在是实现上通常采用原地排序,因此在从后往前的比较过程中,需要反复把已经排好序的逐步向后挪位,为新元素提供插入空间。
手动模拟过程:
括号里是已经排序好的子序列,哨兵是记录的待插入的元素,有序的序列中是从后往前和哨兵进行比较的。
具体代码:
/*对顺序表L做直接插入排序 一边比较一边移动*/
void InsertSort(SqList *L)
{
int i,j;
/*从第二个开始,将A[2]~A[n]插入前面已排序序列*/
for(int i=2;i<=L->length;i++)
{
if(L->r[i]<L->r[i-1])
{
L->r[0]=L->r[i];//设置i位置为哨兵
for(j=i-1;L->r[j]>L->r[0];j--)
{
L->r[j+1]=L->r[j];
}
L->r[j+1]=L->r[0];/*插入到正确位置*/
}
}
}
该算法的特点是:边比较边移动,直到找到正确的位置插入;设置哨兵与有序序列中的元素进行比较,从后往前找到正确的位置,然后进行插入。
时间复杂度:
最好时间复杂度:排序完全是从小到大,不需要进行元素的移动,只需要进行n-1次的比较,时间复杂度为O(n).
最坏时间复杂度:排序完全倒序,总的比较次数和移动次数都最大,时间复杂度为O(n的平方)
平均情况下的时间复杂度为O(n的平方)
空间复杂度:
仅使用了常数个辅助单元,因此空间复杂度为O(1)。
二、折半插入排序:对于数据量不大的排序表,折半插入排序能表现出很好的性能。
折半插入排序与直接插入排序:
1、直接插入排序是边比较边移动;而折半插入排序是先通过折半减少元素比较次数的方式,找到待插入元素的位置,再进行插入。
2、直接插入排序适用于链式和顺序存储的线性表,但是折半插入排序仅仅适合顺序存储的线性表
2、他俩都是一种稳定的排序算法。
L->r[0]=L->r[i];//"哨兵"作用
low=1;high=i-1;/*设置折半查找的范围,有序序列,0不记,是哨兵*/
while(low<=high)
{
mid=(low+high)/2;/*折半*/
if(L->r[mid]>L->r[0])
{
high=mid-1;/*则查找左半子表 */
}
else
{
low=mid+1;/*则查找右半子表*/
}
}
for(j=i-1;j>=high+1;--j)
{
L->r[j+1]=L->r[j];/*统一后移操作*/
}
L->r[high+1]=L->r[0];/*插入操作*/
}
时间复杂度:折半插入排序通过折半减少元素比较次数,时间复杂度约为:O(nlogn),该比较次数与待排序表的初始状态无关,仅取决于表中元素的个数n,移动次数并未改变,它依赖于待排序表的初始状态,因此折半插入排序的时间复杂度为O(n的平方)。
三、希尔排序(缩小增量排序):插入排序适合那种基本有序的排序表和数据量不大的,因此,希尔排序出现。
基本思想:将带排序表分割为若干个形如L[i,i+d,i+2d....]的”特殊"子表,把相隔某个增量的元素构成一个子表,对每个子表使用直接插入排序,当整个表中的元素呈现“基本有序”时,再对全体记录进行一次直接插入排序。可结合下图理解:
void shell(SqList *L)
{
int i,j,dk;
for(dk=L->length/2;dk>=1;dk=dk/2) /*增量变化*/
{
for(i=dk+1;i<=L->length;i++)
{
if(L->r[i]<L->r[i-dk])
{
L->r[0]=L->r[i];
for(j=i-dk;j>0&&L->r[0]<L->r[j];j-=dk)
{
L->r[j+dk]=L->r[j];
}
L->r[j+dk]=L->r[0];
}
}
}
}
希尔排序仅适用于顺序存储的线性表。