1.     减治法(增量法)

  直接插入排序,借鉴了减治法的思想(也有人称之为增量法)。

  • 减治法:对于一个全局的大问题,和一个更小规模的问题建立递推关系。
  • 增量法:基于一个小规模问题的解,和一个更大规模的问题建立递推关系。

  

  可以发现,无论是减治法还是增量法,从本质上来讲,都是基于一种建立递推关系的思想来减小或扩大问题规模的一种方法。

  

  很显然,无论是减治法还是增量法,其核心是如何建立一个大规模问题和一个小规模问题的递推关系。根据应用的场景不同,主要有以下3种变化形式:

  • 减去一个常量。(直接插入排序)
  • 减去一个常量因子。(二分查找法)
  • 减去的规模可变。(辗转相除法)

2.     直接插入排序

  直接插入排序(straight insertion sort),有时也简称为插入排序(insertion sort),是减治法的一种典型应用。其基本思想如下:

  • 对于一个数组A[0,n]的排序问题,假设认为数组在A[0,n-1]排序的问题已经解决了。
  • 考虑A[n]的值,从右向左扫描有序数组A[0,n-1],直到第一个小于等于A[n]的元素,将A[n]插在这个元素的后面。

  很显然,基于增量法的思想在解决这个问题上拥有更高的效率。

  直接插入排序对于最坏情况(严格递减的数组),需要比较和移位的次数为n(n-1)/2;对于最好的情况(严格递增的数组),需要比较的次数是n-1,需要移位的次数是0。当然,对于最好和最坏的研究其实没有太大的意义,因为实际情况下,一般不会出现如此极端的情况。然而,直接插入排序对于基本有序的数组,会体现出良好的性能,这一特性,也给了它进一步优化的可能性。(希尔排序)

  直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1),同时也是稳定排序。

  下面用一个具体的场景,直观地体会一下直接插入排序的过程。

场景:

现有一个无序数组,共7个数:89 45 54 29 90 34 68。

使用直接插入排序法,对这个数组进行升序排序。

89 45 54 29 90 34 68

45 89 54 29 90 34 68

45 54 89 29 90 34 68

29 45 54 89 90 34 68

29 45 54 89 90 34 68

29 34 45 54 89 90 68

29 34 45 54 68 89 90

直接插入排序的 Java 代码实现:

 1     public static void basal(int[] array) {
 2         if (array == null || array.length < 2) {
 3             return;
 4         }
 5         // 从第二项开始
 6         for (int i = 1; i < array.length; i++) {
 7             int cur = array[i];
 8             // cur 落地标识,防止待插入的数最小
 9             boolean flag = false;
10             // 倒序遍历,不断移位
11             for (int j = i - 1; j > -1; j--) {
12                 if (cur < array[j]) {
13                     array[j + 1] = array[j];
14                 } else {
15                     array[j + 1] = cur;
16                     flag = true;
17                     break;
18                 }
19             }
20             if (!flag) {
21                 array[0] = cur;
22             }
23         }
24     }

basal

3.     优化直接插入排序:设置哨兵位

  

  仔细分析直接插入排序的代码,会发现虽然每次都需要将数组向后移位,但是在此之前的判断却是可以优化的。

  不难发现,每次都是从有序数组的最后一位开始,向前扫描的,这意味着,如果当前值比有序数组的第一位还要小,那就必须比较有序数组的长度n次。这个比较次数,在不影响算法稳定性的情况下,是可以简化的:记录上一次插入的值和位置,与当前插入值比较。若当前值小于上个值,将上个值插入的位置之后的数,全部向后移位,从上个值插入的位置作为比较的起点;反之,仍然从有序数组的最后一位开始比较。

设置哨兵位优化直接插入排序的 Java 代码实现:

 1     // 根据上一次的位置,简化下一次定位
 2     public static void optimized_1(int[] array) {
 3         if (array == null || array.length < 2) {
 4             return;
 5         }
 6         // 记录上一个插入值的位置和数值
 7         int checkN = array[0];
 8         int checkI = 0;
 9         // 循环插入
10         for (int i = 1; i < array.length; i++) {
11             int cur = array[i];
12             int start = i - 1;
13             // 根据上一个值,定位开始遍历的位置
14             if (cur < checkN) {
15                 start = checkI;
16                 for (int j = i - 1; j > start - 1; j--) {
17                     array[j + 1] = array[j];
18                 }
19             }
20             // 剩余情况是:checkI 位置的数字,和其下一个坐标位置是相同的
21             // 循环判断+插入
22             boolean flag = false;
23             for (int j = start; j > -1; j--) {
24                 if (cur < array[j]) {
25                     array[j + 1] = array[j];
26                 } else {
27                     array[j + 1] = cur;
28                     checkN = cur;
29                     checkI = j + 1;
30                     flag = true;
31                     break;
32                 }
33             }
34             if (!flag) {
35                 array[0] = cur;
36             }
37         }
38     }

optimized_1

4.     优化直接插入排序:二分查找法

  优化直接插入排序的核心在于:快速定位当前数字待插入的位置。在一个有序数组中查找一个给定的值,最快的方法无疑是二分查找法,对于当前数不在有序数组中的情况,官方的 JDK 源码 Arrays.binarySearch() 方法也给出了定位的方式。当然此方法的入参,需要将有序数组传递进去,这需要不断地组装数组,既消耗空间,也不现实,但是可以借鉴这方法,自己实现类似的功能。

  这种方式有一个致命的缺点,导致虽然效率高出普通的直接插入排序法很多,但是却不被使用。就是这种定位方式找到的位置,最终形成的数组会打破排序算法的稳定性。既然一定会打破稳定性,那么为什么不使用更优秀的希尔排序呢?

二分查找法优化直接插入排序的 Java 代码实现:

 1     // 利用系统自带的二分查找法,定位插入位置
 2     // 不稳定排序
 3     public static void optimized_2(int[] array) {
 4         if (array == null || array.length < 2) {
 5             return;
 6         }
 7         for (int i = 1; i < array.length; i++) {
 8             int cur = array[i];
 9             int[] sorted = Arrays.copyOf(array, i);
10             int index = Arrays.binarySearch(sorted, cur);
11             if (index < 0) {
12                 index = -(index + 1);
13             }
14             for (int j = i - 1; j > index - 1; j--) {
15                 array[j + 1] = array[j];
16             }
17             array[index] = cur;
18         }
19     }

optimized_2

 1     // 自己实现二分查找
 2     // 不稳定排序
 3     public static void optimized_3(int[] array) {
 4         if (array == null || array.length < 2) {
 5             return;
 6         }
 7         for (int i = 1; i < array.length; i++) {
 8             int cur = array[i];
 9             // 二分查找的高位和低位
10             int low = 0, high = i - 1;
11             // 待插入的索引位置
12             int index = binarySearch(array, low, high, cur);
13             for (int j = i - 1; j > index - 1; j--) {
14                 array[j + 1] = array[j];
15             }
16             array[index] = cur;
17         }
18     }
19
20     // 二分查找,返回待插入的位置
21     private static int binarySearch(int[] array, int low, int high, int cur) {
22         while (low <= high) {
23             int mid = (low + high) >>> 1;
24             int mVal = array[mid];
25             if (mVal < cur) {
26                 low = mid + 1;
27             } else if (mVal > cur) {
28                 high = mid - 1;
29             } else {
30                 return mid;
31             }
32         }
33         // 未查到
34         return low;
35     }

optimized_3

5.     简单的性能比较

  

  最后,通过以下程序,简单地统计一下上述各种方法的运行时间。

 1     public static void main(String[] args) {
 2
 3         final int size = 100000;
 4         // 模拟数组
 5         int[] array = new int[size];
 6         for (int i = 0; i < array.length; i++) {
 7             array[i] = new Random().nextInt(size) + 1;
 8         }
 9
10         // 时间输出:纳秒
11         long s1 = System.nanoTime();
12         StraightInsertion.basal(array);
13         long e1 = System.nanoTime();
14         System.out.println(e1 - s1);
15     }

test

执行结果:

结论如下:

  • 在某些特定场景下,由于入参的条件不同,不能执着于 JDK 给的现有方法,自定义的实现效率,可能高于源码的效率。
  • 对于小规模的数组,优化的结果和预想的向左,效率比不上最初的方法。原因在于本身只是对于判断的优化,而不是执行次数的优化。在每次循环中,加上更多的计算去优化这个判断,在小数组上对于整个排序的效率,反而是一种伤害。
  • 大规模数组,二分查找优化效率明显。

相关链接:

https://github.com/Gerrard-Feng/Algorithm/blob/master/Algorithm/src/com/gerrard/sort/StraightInsertion.java

PS:如有描述不当之处,欢迎指正!

转载于:https://www.cnblogs.com/jing-an-feng-shao/p/6165094.html

排序算法(三) —— 直接插入排序相关推荐

  1. 排序算法三:插入排序

    假设前面的数已经排好序插入一个数依次与前面比较 public static void insertionSort(int[] arr) {if (arr == null || arr.length & ...

  2. php代码编写直接插入排序算法,PHP排序算法之直接插入排序(Straight Insertion Sort)实例分析...

    本文实例讲述了PHP排序算法之直接插入排序(Straight Insertion Sort).分享给大家供大家参考,具体如下: 算法引入: 在这里我们依然使用<大话数据结构>里面的一个例子 ...

  3. 插入排序 php,PHP排序算法之直接插入排序(Straight Insertion Sort)实例分析

    本文实例讲述了PHP排序算法之直接插入排序(Straight Insertion Sort).分享给大家供大家参考,具体如下: 算法引入: 在这里我们依然使用<大话数据结构>里面的一个例子 ...

  4. 插入排序java_排序算法之直接插入排序Java实现

    排序算法之直接插入排序 舞蹈演示排序: 冒泡排序: http://t.cn/hrf58M 希尔排序:http://t.cn/hrosvb 选择排序:http://t.cn/hros6e 插入排序:ht ...

  5. 3.图解排序算法(三)之堆排序

    作者: dreamcatcher-cx 出处: http://www.cnblogs.com/chengxiao/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在页面明显位 ...

  6. 排序算法(2)直接插入排序

    排序算法(2)直接插入排序 原理:将数组中的所有元素依次和前面的已经排好序的元素相比较(依次)  ,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过. 代码实现: void InsertS ...

  7. 我的Java开发学习之旅------Java经典排序算法之二分插入排序

    一.折半插入排序(二分插入排序) 将直接插入排序中寻找A[i]的插入位置的方法改为采用折半比较,即可得到折半插入排序算法.在处理A[i]时,A[0]--A[i-1]已经按关键码值排好序.所谓折半比较, ...

  8. 七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)...

    写在前面: 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的随意序列,又一次排列成一个按keyword有序的序列.因此排序掌握各种排序算法很重要. 对以下介绍的各个排序,我们假定全部排 ...

  9. C语言——十四种内部排序算法【直接插入排序-冒泡排序-选择排序-插入排序-希尔排序-归并排序-快速排序-堆排序-折半插入排序-二分查找-路插入排序-表插入排序-简单选择排序-直接选择排序-树形选择】

    目录: 一:插入排序 A:直接插入排序 1.定义: 2.算法演示 实例1: 3.基本思想 4.排序流程图 实例1: B:希尔排序 1.定义: 2.算法演示 实例2: C:其他插入排序 a:折半插入排序 ...

  10. 【Java】5大排序算法总结(插入排序+希尔排序+选择排序+堆排序+冒泡排序)

    快速导航: 1. 稳定性 2 . 插入排序 3. 希尔排序 4. 选择排序 5. 堆排序 6 冒泡排序 1. 稳定性 两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法 ...

最新文章

  1. java中常用的字符串的截取方法
  2. linux查看安装的所有内核,Linux怎么查看系统已安装内核
  3. bootstrap-dist的下载和使用bootstrap可视化布局代码无样式解决
  4. 2019-06-13 Java学习日记之MySql
  5. 多线程的等待唤醒机制
  6. java 编码格式 utf 8_关于java生成UTF-8编码格式文件的诡异问题
  7. 无法验证的代码未能通过策略检查
  8. STM32 USB接口 一键下载电路详解与过程分析
  9. 大学物理第三版朱峰课后答案详解_大学物理 第三版 [朱峰 主编] 2014年版
  10. Chrome浏览器保存整个网页为图片的方法
  11. ubuntu phpmyadmin php5.3,ubuntu中怎么下载安装phpmyadmin
  12. 1.8 微信小程序 使用canvas绘制奥运会五环
  13. VMware下如何虚拟软盘启动
  14. 【CodeForces 767C】Garland (树形DP)
  15. 网站打开速度与服务器的关系,网站打开速度慢对SEO的影响
  16. 产品标题什么时候进行优化,提高权重,标题优化的技巧方法
  17. win7系统安装telnet服务器,Win7怎样安装telnet服务?
  18. R语言 去掉NA求均值
  19. AI音箱的原理,小爱同学、天猫精灵、siri。
  20. 英语c开头语言,C开头的英语短语集锦

热门文章

  1. nginx tcp代理_小猿圈linux教程之Nginx负载均衡TCP/UDP流
  2. Silverlight for Windows Phone 开发学习笔记(-)
  3. luogu4360 锯木厂选址 (斜率优化dp)
  4. SDUST 作业10 Problem D 魔方阵
  5. PhotoshopCS2相关教程网址
  6. HDU 3065 病毒侵袭持续中(AC自动机 模板)题解
  7. python 高阶函数之filter
  8. ThinkPHP5.1设置404页面
  9. Linux服务器管理神器-IPython
  10. 数据泵工具导出的步骤(用于Oracle11G和10G之间的相互转换)