插入排序

来源:互联网 发布:雅思8.5 知乎 编辑:程序博客网 时间:2024/06/11 08:48

一,目标
本节内容主要讲述了插入排序,包括直接插入排序与其改进版希尔排序(默认递增排列)。

二,原理
//注:这里只提供算法相应的解释与实现过程,并不讲解其来历,以及一些具体的计算(如时间复杂度等),如有兴趣,请读者自行了解

2.1 插入排序

讲起插入排序,我想对于打过扑克牌的童鞋来讲是再熟悉不过了,每次揭牌的时候,为了使牌序清晰,你都会把最小的牌放到最左边(假定),然后比它大的次之。此刻你新揭了一张牌,为了找到它的位置,你就需要与手上已有的牌从最右边依次进行比较,假如比最右边的大,则把它直接插入(现在这张牌便成了最大的一张),如果比其小,则一直比较到一个合适的位置,使得它右边的牌比它大,它左边的牌比它小,或者直接是最小的,则插入到最左边。

现在我们以一个整型数组为例:5,4,1,8,9,2,1,3

假定我们有一个已排好序的arry,此刻,arry.size==0;
1,将5放入arry,此刻size=1,已经是有序的了,所以不用再比较。

2,将4加入arry,此刻size=2,需要比较,4所在的位置为1(数组从0开始),4<5 ?
很显然这是成立的,所以,把5放到4所在的位置,4再与5前面的数进行比较,因为已经到了最左边,所以4此刻为最小,即在0位置插入4

3,将1加入arry,此刻size为3,首先1<5成立,所以把5往后挪一位,即:arr[2]=5;
此时1处在位置1,位置1前面还有数,所以用1与4比较,显然1<4,并且已经到了位置0,没有更小的数了,所以把4后移一位,将1插入到0位置
此刻我们的arry中的元素为:1,4,5;为有序的了

4,接下来将8加入arry,此时size=4,因为8直接比它前面的数还大,所以不用再向前比较,它所在的位置就是它的,此时arry:1,4,5,8

5,继续往下进行,直到待排序列都加入到了arry,那么arry就是排好的序列了。

在实际中我们并没有这个arry数组,只有待排序列,上面那么说,是为了更好的阐述,其实我们只需要两个变量i,j便可以达到访问控制了。下面为整个排序的过程,以及当前数组中的次序
注:加粗数字代表”当前需要插入到已排好序列中的数“
-》:代表插入后,数组中元素的位置
第一次:5,4,1,8,9,2,1,3 -》 4,5,1,8,9,2,1,3
第二次:4,5,1,8,9,2,1,3-》1,4,5,8,9,2,1,3
第三次:1,4,5,8,9,2,1,3-》1,4,5,8,9,2,1,3
第四次:1,4,5,8,9,2,1,3-》1,4,5,8,9,2,1,3
第五次:1,4,5,8,9,2,1,3-》1,2,4,5,8,9,1,3
第六次:1,2,4,5,8,9,1,3-》1,1,2,4,5,8,9,3
第七次:1,1,2,4,5,8,9,3-》1,1,2,3,4,5,8,9

2.2 希尔排序
希尔排序是在插入排序的基础上得来的,它是其改良版,因为当待排序列接近有序的时候,我们会发现,插入的效率特别高,例如上例的第三次,与第四次,因为7,8本来就是有序的,所以不用再与前面的进行比较,于是当序列为有序时使用插入排序,时间复杂度可以从O(n^2)->O(n);
那么希尔排序是怎样做到让序列接近有序的呢?它新增加了一个概念我们暂且称之为”步长“,每次以步长为标准将待排序列分成若干个组,然后给每个组中的元素分别进行插入排序,使其局部达到有序,再进行整体排序的过程,设希尔排序的时间度为m,m大于O(n),小于O(n^2);

2.21 步长选择的依据
一般情况下步长选择为待排序列长度的一半,当每用该步长进行了一次插入排序,让步长变为其1/2,当步长为1时就进行了一次完整的插入排序。
以上面的为例,对于待排序列:5,4,1,8,9,2,1,3;长度为8
1,第一次步长为:4
则0-4,1-5,2-6,3-7构成了四组
分别为(5,9),(4,2),(1,1),(8,3)
调整后(5,9),(2,4),(1,1),(3,8)
当前列表:5,2,1,3,9,4,1,8

2,第二次步长为:4/2=2;
则:0,2,4,6 构成一组
1,3,5,7构成一组

为什么步长为4构成了4组,而步长为二构成两组呢?
刚开始,我也没理解,那是因为0+4=4,4+4=8>7(列表长度),所以只能分成2个一组
而0+2=2,2+2=4,4+2=6,则可以分成4个一组了
则总共分了两组:
下标位置:0,2,4,6 与 1,3,5,7
分别为:5,1,9,1 和2,3,4,8
分别进行插入排序,最后得到序列:1,2,1,3,5,4,9,8

3,第三次步长为:2/2=1
所以:0,1,2,3,4,5,6,7分为一组进行插入排序
此时的序列为1,2,1,3,5,4,9,8已经接近有序,此时的插入效率便会特别高了。

三,代码实现

void copy_insert(int* arry, int n){    assert(arry != NULL);    for (int i = 1; i < n; i++) //从第一个开始(下标)    {        int temp = arry[i];        int j;        for (j = i - 1; j >= 0 && arry[j] < temp; j--) //每次插入arry[i]        {            //当满足的时候,将相应位置后移            arry[j + 1] = arry[j]; //后移一位        }        //j--之后不满足,所以j+1便是应该插入的位置        arry[j + 1] = temp;    }}
void copy_shell(int* arry, int n){    assert(arry != NULL);    int gas;    for (gas = n / 2; gas > 0; gas /= 2) //步长选择    {        //插入排序,当gas为1时,便是普通的插入排序        for (int i = gas; i < n; i+=gas)        {            int temp = arry[i];            int j;            for (j = i - gas; j >= 0 && arry[j]>temp; j-=gas)            {                arry[j + gas] = arry[j];            }            arry[j + gas] = temp;        }    }}

四,总结
1,看书时有可能是因为编书人的表达方式,造成你难以理解相应的内容,可以选择多换几本不同的书。没准就理解了,我在理解shell排序时便没搞懂其意思,在网上看代码,也解决不了

2,当你不明白一个问题时,不要急着去实现代码,自己懂了,代码自现

3,size_t,size_t, size_t,虽然它的使用确实提高了软件的可移植性,但是在使用时一定要注意,例如size_t i=1;i-1会等于多少?会等于0吗?