插入排序,顾名思义,就是将一个待排序的记录按照关键字的大小,插入到前面已排序好的子序列中,直到全部的记录都插入完成为止。三个重要的插入排序算法:直接插入排序、折半插入排序、希尔排序。
1.直接插入排序
(假定为从小到大排序)
基本思路:定义一个中间元素tep,从一个无序序列的第二个元素开始(此时这个元素的前面为有序序列,后面为无序序列),将其与第一个元素比较,若a[1]<a[0],则将a[1]的值赋给中间元素tep,而后将从a[0]开始的元素全部后移一位(无须担心a[1]的值被覆盖,因为早已将其转移到tep中),再将tep赋给a[0];若a[1]>a[0],则无序序列后移一位,最后重复步骤。
说白了,就是a[i]前的序列为有序的,a[i]后的序列为无序的,用a[i]和有序序列一一比较,a[i]小则插入,大则后移,a[i]并入到有序中,进行a[i+1]的比较。
typedef int Elemtype;
void InsertSort(Elemtype a[],int n)
{
int j=0,tep=0;
for(int i=1;i<n;i++)//从第二个元素开始插入
{
if(a[i]<a[i-1])
{
tep=a[i];
for(j=i-1;tep<a[j];j--)//从有序表的后往前依次比较,便于交换后移,从前面开始太过复杂
a[j+1]=a[j];//将有序表后移
a[j+1]=tep;//此时j已经往前移了一位,所以要+1
}
}
}
空间效率:空间复杂度为O(1)。
时间效率:最好最坏情况平均下,时间复杂度为O(n^2)。
适用性:顺序存储和链式存储的线性表。
2.折半插入排序
(假定为从小到大排)
基本思路:在有序序列中使用二分查找查找插入位置,其他步骤同直接插入排序一样。
typedef int Elemtype;
void BinarySort(Elemtype a[],int n)
{
for(int i=1;i<n;i++)
{
int tep=a[i];
int left=0,right=i-1;//在已排序序列中用二分查找要插入的位置
while(left<=right)
{
int mid=(left+right)/2;
if(tep<a[mid])
right=mid-1;
else//tep=a[mid]时无须替换,left后移就行了
left=mid+1;
}
for(int j=i-1;j>=left;j--)//后移一位,注意是j>=left
a[j+1]=a[j];
a[left]=tep;//为什么是left?因为此时的left是mid的后面一个,也就是刚好需要替换的一个
}
}
空间效率:空间复杂度为O(1)。
时间效率:最好最坏情况平均下,时间复杂度为O(n^2)。
3.希尔排序
基本思路:先将数组按照一定间隔(称为增量)分成若干个子序列,对每个子序列进行插入排序,然后逐次减小增量,重复上述过程,直到增量为1时执行最后一次插入排序,使得整个序列有序。
希尔排序的关键在于选择合适的增量序列。常见的增量序列有希尔增量(N/2)、Sedgewick增量等。一般情况下,希尔增量被认为是较优的选择。
希尔排序的实现过程:
初始化增量gap,设数组长度为n;
如果gap大于等于1,则继续执行步骤3,否则退出排序;
将数组分为gap个子序列,对每个子序列进行插入排序;
减小增量gap,重复步骤2。
void shellSort(int arr[],int n)
{
// 选择一个递减的增量序列
for(int gap=n/2;gap>0;gap/=2)
{
// 对每个子数组进行插入排序
for(int i=gap;i<n;i++) //此语句表示一共有n-gap个子序列
{
int tep=arr[i];
int j=i-gap;
while(j>=0 && arr[j]>tep)
{
arr[j+gap]=arr[j]; //将大的数换到后面去
j-=gap;//一组一组排序
}
arr[j+gap]=tep;//没执行while时相当于没操作;执行了while时,此时的j是负数,加上gap就是将tep赋给前者。
}
}
}
举个栗子:
空间效率:空间复杂度为O(1)。
时间效率:n在某特定范围内的时间复杂度约为O(n^1.3);最坏情况下,时间复杂度为O(n^2)。
适用性:仅适用于线性表为顺序存储的情况。