插入排序
思想
插入怕排序是一种简单的排序方法,他的基本操作是将一个数据插入到已有序的序列中,从而得到序列数加一的新的序列
插入排序中,我们先将最初始的有序序列数定为第一个数字,即在下列数据中为1,从第二个开始,当前有序列为“1”
5和有序序列从头开始比较,发现5应该在1的后边,所以新的有序序列为“1,5”
然后第三个数字3,和有序序列从头开始比较,发现3应该在1后边,所以我们把1后边的数据后移腾开位置,将3插入进来,此时有序序列为“1,3,5”
第四个数字6,比较后找到位置,插入到5之后,新的序列变为“1,3,5,6”
第五个数字4,找到位置,插入到3后,3原本后边的数据后移,新序列为“1,3,4,5,6”
上述过程中我们发现,大概步骤分为,1.找到插入位置2.移动数据3.插入数据,所以实现如下
void insertsort(int *arr,int size)
{
int i,j,tmp;
for(i = 1;i < size;++i)//下标从0开始,初始有序列为0,从1开始排序
{
tmp = arr[i];//第一个待排数字取出
for(j = 0;j < i;++j)//在有序列中寻找合适位置
{
if(arr[j] > tmp)//找到第一个大于待排数字的值,跳出循环
break;
}
for(int k = i - 1;k >= j;--k)//将大于待排数字以及之后的数据后移
{
arr[k + 1] = arr[k];
}
arr[j] = tmp;//代排数字插入
}
}
时间复杂度与空间复杂度
分析空间复杂度O(1),时间复杂度为O(n2),但是再仔细分析后发现,此排序在数据本身有序的情况下时间复杂度,还是O(n2),排序分析其实不该如此,在数据本身有序时候,时间复杂度应该退化为O(n),其关键在于我们在有序列中寻找插入位置的实现
如上图,当3要在有序列“1,2”中寻找合适插入位置,若是按照上述代码从头开始寻找,我们得从1开始找,实际并不需要如此,因为有序列已经有序,我们只需从最后一个开始比较,遇到合适位置插入即可,3从2开始倒序寻找,发现2之后就是合适位置,直接插入即可,所以我们的寻找位置实现只需要从有序列最后一个往前找即可,插排中,这算是一个容易踩到的坑。
修改后我们的实现如下(还做了一些小小的优化)
void insertsort(int *arr,int size)
{
int i,j,tmp;
for(i = 1;i < size;++i)
{
tmp = arr[i];//去除待排数字
for(j = i - 1;j >=0 ;--j)//在有序列中从后往前找插入位置
{
if(arr[j] <= tmp)//当遇到第一个小于或者等于待排数字的值,即找到有效位置
break;
else//否则遇到的都是大于待排数据的值,直接将该数字后移
arr[j + 1] = arr[j];
}
arr[j + 1] = tmp;//无论如何,循环结束,找到有效位置插入
}
}
在此代码中,我们不光将寻找方式修改,并且将后移数据操作合并至寻找位置之中,因为第一个小于等于待排数据的位置的下一个才是待排数据的有效位置,如果没找到该位置之前,只要遇到大于待排数据的值,早晚我们都要后移他,所以我们一发现就移动,就不需要后续做其他的处理,实现就变成了,遇到大的数字,后移,遇到第一个小于等于的数字,即找到位置并跳出循环,然后插入即可。当前实现,在序列有序的情况下,时间复杂度退化为O(n),空间复杂度还是O(1)。
稳定性
稳定性:序列“3,5,3‘ ”,在排序后为“3,3’,5”即3,3‘没有交换,则为稳定,在此排序中我们的条件判断是“ if(arr[j] <= tmp) ”,当3’进行插入时,遇到3的处理时跳出循环,5后移,所以3‘会插入到3的后面,所以稳定,有人会发现,如果我们把该条件判断改为“ if(arr[j] < tmp) ”,3‘就会在3的前边,就不稳定了,所以要注意的一点就是:稳定性时针对算法本身,并不针对某一次实现,这里算法本事是稳定的,但是因为实现问题,使得他不稳定,不能说算法本事是不稳定的。所以更好的一个判断方法就是看算法本身的思想,有没有进行跳跃式的交换数据,如果每次交换我们都是相邻的两个数字进行交换,那么稳定,跳过有交换是跳过若干个数据进行交换,我们不能确定跳过了什么数据,所以就是不稳定的。插排中,我们的待排数据在和有序数据倒序比较时都是,我与你比较,你比我大,我就在你前边,往前走一个,下一个数据,我比你大,我两交换我在插你前边,都是相邻交换,故是稳定的。