小学生图解排序算法:③直接插入排序

直接插入排序

含义:将一个记录插入到一个有序列表中的合适位置,并要求新列表依然是有序的。

算法思路

以对一个给定长度为n的无序数组a[]从小到大排列为例。
1. 假定数组的第二位a[1]至末位a[n-1]目前不是该数组的元素,而是待插入的值。
2. 按照1中的假定,此时数组a[]只有1个元素,显然,只有1个元素的数组可以视为有序数组。
3. 将a[1]值与数组a[]所含的1个元素比对,如a[1]较小则将另一个比对元素后移以便腾出位置,将a[1]的值插入最后腾出的位置。此时,数组a[]有2个元素且是有序的。
4. 将a[2]值与数组a[]所含的2个元素依次比对,如a[2]较小则将另一个比对元素后移以便腾出位置,直到a[2]不是较小值或已比到数组首位,则将a[2]的值插入最后腾出的位置上。此时,数组a[]有3个元素且是有序的。
5. ……
6. 将a[n-1]值与数组a[]所含的n-1个元素依次比对,如a[n-1]较小则将另一个比对元素后移以便腾出位置,直到a[n-1]不是较小值或已比到数组首位,则将a[n-1]的值插入最后腾出的位置上。此时,数组a[]全部排序完成,有n个元素且是有序的。


图解过程
依然以图解来说明排序算法的过程,更加直观易懂。


图解说明

  • 从图解初始我们可以看到,首先我们将长度为n的数组a[]视为只包含其首位元素,其余元素(a[1]~a[n-1])皆视为待插入的记录。每次插入1个记录,显然,我们需要插入n-1次。

- 若用 i 表示插入次序,显然其取值范围是 1 <= i <= n -1。(n-1是末位下标,可以取值到,所以是<=号。)
- 当然你也可以写成1 <= i < n。(n是数组长度,下标取不到该埴,所以是<号)

  • 于是,我们有了这样的代码来表示每次插入一个值,如:
for(int i = 1; i < n; i++){
}
  • 在图解中,每次插入值时,我们会先用temp保存要插入的值,以防止左侧值右移时覆盖掉该值,另一个作用是将该值插入到寻找到的合适插入位置(即最终腾出的位置)。完善一下上面的代码,则是:
for(int i = 1; i < n; i++){
   int temp = a[i];

   //这里是寻找合适插入位置、循环比对大小的逻辑代码

   a[x] = temp;//x为最终腾出的下标位
}
  • 上面的代码中,很明显空缺了一块非常重要的逻辑,就是如何寻找到合适的插入位置。接下来按照图解情况来分析一下。

    1. 首先数组中有1个元素,即a[0]。
    2. 当第1次插入元素时,插入在a[1]位,将其值存在temp中,并与其前一位a[0]比对。插入值更小,将a[0]复制给a[1],腾出了a[0]位。因为已经比对到数组首位了,所以停止比对,然后将temp值插入以a[0]。(请注意,这里出现了第一种停止比对的条件)
    3. 当第2次插入元素时,插入在a[2]位,将其值存在temp中,并与其前一位a[1]比对。插入值更小,将a[1]复制给a[2],腾出了a[1]位;temp继续和前一位a[0]比对,插入值更小,将a[0]复制给a[1](刚才的腾出位被占,但此时腾出了新的位置a[0])。因为已经比对到数组首位了,所以停止比对,然后将temp值插入以a[0]。
    4. 当第3次插入元素地,插入在a[3]位,将其值存在temp中,并与其前一位a[2]比对。插入值更大;因为前面的数组元素已经是从小到大排列了,所以插入值既然比a[2]大,那么也没有必要再和前面的元素比大小了。所以停止比对。因为没有腾出位置,所以将temp值插入末位a[3]。(请注意,这里出现了第二种停止比对的条件)
    5. …………
    6. 当第 i 次插入元素时,插入在a[ i ]位,将其值存在temp中,并与其前一位a[ i-1 ]比对。如插入值更大,说明其比前面的所有值都更大(因为前面已经是从小到大排列了),停止比对,并将temp值插入到末位。如插入值更小,则将参与比对的元素值覆盖后一位,腾出当前位置。temp继续与前一位比对,如此重复,直到出现上文中任意一个停止比对条件,然后将temp值插入以最后的腾出位置。
  • 将上面的比对插入分析,会发现在每次插入新记录时,都是从插入位置的前一位开始比对,若用 j 表示与temp比对的元素的下标,则 j 的初始值为:

    j = i - 1;

  • 若某次比对后,没有触发停止比对的条件,则其前一位继续与temp比较,也就是参与比对的元素下标会发生这样的变化:

    j = j-1; // j–

  • 图解中出现了2种停止比对的条件,一是比完了首位a[0],一是比对的值大于要插入值temp,即:

    j >= 0 && a[ j ] > temp;

  • 当经过比对,插入值temp比参与比对的值小时,该比对的值向移一位,腾出当前位置,即:

  • 综合以上分析,我们可以写出算法的核心代码了。


核心代码

public static void insertSort(int[] a){
   int i, j, temp;
   for(i = 1; i < a.length; i++){
      temp = a[i];
      for(j = i - 1; j >= 0 && temp < a[j]; j--){
         a[j + 1] = a[j]; // j 是每次腾出的位置   
      }
      a[j + 1] = temp;
   }
}
  • 代码已经完成了,或许有人还有一个疑问,为什么最后temp是赋值给a[ j+1]而不是a[ j ]呢?不是说 j 才是腾出的位置吗?

请注意,最后一次执行内部for循环时,将 j 位置上的值移给后一位 j+1位置了, j 位就是最后腾出的位置。然后!然后 j 做了什么行为? j - - 。所以for循环结束时,j 自身减1了,所以跳出for循环后,要 j+1 才是真正腾出的位置。

在图解中也能比较好地说明这个细节,每次比对符合条件时,将当前值移给后一位之后,当前位就是腾出的位置。接着temp会继续与更前一位继续比对,所以此时,刚才腾出的位置就是当前位的右边一位了。


算法稳定性

直接插入排序算法是稳定的算法。

在图解示例中,我们发现2个8的前后相对位置在排序前和排序完成后并未发生改变。

当然,如果你非要把代码改成下面这样,它确实可以变成不稳定的算法,毕竟蹩脚的程序员能毁天毁地毁宇宙。

for(int j = i - 1; j >= 0 && temp <= a[j]; j--){ 
   //把temp < a[j] 改为 temp <= a[j]
}

声明

本文为个人学习笔记,如有细节错误或描述歧义,请留言告知,谢谢!
本文首发于博客专栏: http://Windows9.Win/insert_sort_algorithm/

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值