插入类排序的基本思想是:
取数组中的第一个数,因为只有一个数,所以认定他为有序的,将剩下的数依次插入有序序列中,并保持他为有序,插入完毕,排序完毕
插入排序的步骤可以分为三步:
1.找到应该插入的位置
2.将这个位置后面所有的数依次往后移动一个单位
3.将要插入的数字插入应该插入的位置
一.直接插入排序
void Insert_Sort(int a[],int n)
{
for(int i=1;i<n;i++)
{
int j=i-1;
int temp=a[i];
while(a[j]>a[i])
{
j--;
}
int x;
for(x=i-1;x>j;x--)
{
a[x+1]=a[x];
}
a[j+1]=temp;
}
}
直接插入排序的思路脉络很清晰,严格遵守上面三个步骤:
1.while循环找到插入位置j+1;
2.for循环移动a[j+1]到a[n]的元素
3.最后一个语句插入待插入元素
算法时间复杂度分析
根据语句分析:
当数组序列已经完全是有序的时候,这个时候只用执行最外层的for循环,所以,最好情况的时间复杂度为O(n);
而当数组序列完全是倒序的时候,这个时候内层的while循环和for循环,基本都要执行完全到最大,最坏情况的时间复杂度为O(n²)
空间复杂度为:O(1);
二.折半插入排序
在上面的直接排序中:
第一步:找到应该插入的位置时,用了一个for循环,这里占用了不少时间复杂度,可以用折半的思想来改进这一步
代码如下
void HalfInsert_Sort(int a[],int n)
{
for(int i=1;i<n;i++)
{
int low=0;
int high=i-1;
int temp=a[i];
while(low<=high)
{
int mid=(low+high)/2;
if(a[i]<a[mid])
{
high=mid-1;
}
else
{
low=mid+1;
}
}
for(int j=i;j>low;j--)
{
a[j]=a[j-1];
}
a[low]=temp;
}
}
在上述过程中,运用了折半的思想改造了直接插入排序的第一步:也就是while(low<=high)语句
这里的low<=high记住,如果有=,则说明low=high之后还要循环一次,具体的根据循环一次还有做什么事情来判断!!
时间复杂度分析
在原数组已经基本有序的情况下,外层循环时间复杂度为n,不管怎样,while循环的时间复杂度肯定为log2(n)(以2为底数,n为顶数),而第二个for循环这个时候不用执行循环体,所以最好情况的时间复杂度为O(nlog2(n));
同样的道理,假如顺序完全倒了,最坏的情况是:外层循环为n,while循环仍然为log2(n),第二个for循环这个时候就为n了,所以这个时候就是n*log2(n)+n²,也就是最坏情况的时间复杂度为O(n²);
可以看到,折半插入排序相对于直接插入排序来说并没有改善总的时间复杂度
空间复杂度仍然为O(1)
三.希尔排序(ShellSort)
希尔排序,通过第一种插入排序和第二种插入排序可以总结:插入排序和折半插入排序都还有一个很重要的因素影响着他们的时间复杂度,那就是原来数组元素的有序程度,如果之前他们基本有序,那时间复杂度就可以降低很多
沿着这种思路,我们可以通过几次预排序,来使得原数组元素基本有序,最后通过依次排序来排序,这种思路会不会改善插入排序的时间复杂度呢
希尔排序的就延续了这种思想:他通过将原数组分为几个子数组,对它们进行直接插入排序,最后再将他们一起进行直接插入排序,这种思路,类似于计算机组成与结构里面的虚拟存储体系里的缓冲,cache,通过“缓冲”来缓解排序算法的时间复杂度压力,代码如下:
void Shell_Insert_Sort(int a[],int n,int delta)
{
for(int i=delta;i<n;i++)
{
int j=i-delta;
int temp=a[i];
while(a[j]>a[i])
{
j-=delta;
}
for(int x=i;x>j+delta;x-=delta)
{
a[x]=a[x-delta];
}
a[j+delta]=temp;
}
}
void Shell_Sort(int a[],int n,int delta[],int m)
{
for(int i=0;i<m;i++)
{
Shell_Insert_Sort(a,n,delta[i]);
}
}
这个就是希尔排序的思路了,通过delta划分子数组
我们看看这个数组到底能不能改进时间复杂度
最好的情况:明显也是n,
最坏的情况:嗯,也是n²;
那么平均情况呢,我看见书上给的是n的1.5次,网上有的给的是1.4次,有的给的是1.2次