注:关于排序算法,博主写过【数据结构排序算法系列】数据结构八大排序算法,基本上把所有的排序算法都详细的讲解过,而之所以单独将java集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过我,让我说说java集合框架中用的哪种排序算法,当时回答错了,(关于面试详细过程请参看:【阿里内推一面】记我人生的处女面)面试结束后看了一下java源码,用的是折半插入排序算法,本来早就打算写此博客,但是因为准备鹅厂的在线考试,而鹅厂在我心中的地位是最高的,为了准备鹅厂的在线考试,自己基本上把所有事情都搁置起来了,把全部的精力都投入到复习中去了,所以一直没动手写。既然java的天才程序员都采用了折半插入排序,那么“此人必有过人之处”,因此得好好了解一下折半插入排序。

我们先从c语言中的折半插入排序算法看起,在此基础之上在来看java集合框架中的源码。

  1. #include<iostream>
  2. using namespace std;
  3. const int len=7;
  4.  
  5. void binaryInsertSort(int * array,int len)
  6. {
  7. for(int i=1;i<len;i++)//与普通的排序一样,外层for循环用来控制排序趟数
  8. {
  9. int x=array[i];
  10. int low=0,high=i-1;//low与high的初始化很重要,因为i从1开始,所以low=0,high=i-1,这样就能保证数组中的每一个
  11. //元素参与排序,教材上的low=1是错误的,因为教材上将数组中的第0位作为监视哨而未参与排序。
  12. while(low<=high)//寻找待插入的位置
  13. {
  14. int mid=(low+high)/2;
  15. if(x<array[mid])
  16. high=mid-1;
  17. else
  18. low=mid+1;
  19. }
  20. for(int j=i-1;j>=low;j--)//将记录向后移动
  21. {
  22. array[j+1]=array[j];
  23. }
  24. array[low]=x;//插入记录
  25. }
  26. }
  27. int main()
  28. {
  29. int a[len]={7,0,4,5,1,2,3};
  30. binaryInsertSort(a,len);
  31. for(int i=0;i<len;i++)
  32. cout<<a[i]<<' ';
  33. cout<<endl;
  34. }

可以看到折半插入排序的思想是基于折半查找的,即对有序表进行折半查找,其性能较好,所以可将折半查找的思路运用到排序中一个数组中的元素虽然刚开始不是有序的,但是可以通过折半查找的同时构造有序表,即折半插入排序算法即是通过折半查找构造有序序列,然后在已构造的部分有序序列中运用折半查找插入元素,最终直至整个表排好序为止。

程序运行结果如下:

经过上述c语言代码的讲解,下面我们来看一下java的那些天才设计者们是如何java实现该算法的以及分析一个为何那些天才们看上的不是我们普通程序员最喜欢的快速排序而是折半插入排序。

下面是java中TimSort类中的sort源码,而java集合中的类调用的sort方法最终会调用它

  1. static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
  2. T[] work, int workBase, int workLen) {
  3. assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
  4.  
  5. int nRemaining = hi - lo;
  6. if (nRemaining < 2)
  7. return; // Arrays of size 0 and 1 are always sorted
  8.  
  9. // If array is small, do a "mini-TimSort" with no merges
  10. if (nRemaining < MIN_MERGE) {
  11. int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
  12. binarySort(a, lo, hi, lo + initRunLen, c);
  13. return;
  14. }

可以看到在TimSort类中最终会调用binarySort方法,即折半插入排序,我们来看一下其源码:

  1. /**
  2. * Sorts the specified portion of the specified array using a binary
  3. * insertion sort. This is the best method for sorting small numbers
  4. * of elements. It requires O(n log n) compares, but O(n^2) data
  5. * movement (worst case).
  6. *
  7. * If the initial part of the specified range is already sorted,
  8. * this method can take advantage of it: the method assumes that the
  9. * elements from index {@code lo}, inclusive, to {@code start},
  10. * exclusive are already sorted.
  11. *
  12. * @param a the array in which a range is to be sorted
  13. * @param lo the index of the first element in the range to be sorted
  14. * @param hi the index after the last element in the range to be sorted
  15. * @param start the index of the first element in the range that is
  16. * not already known to be sorted ({@code lo <= start <= hi})
  17. * @param c comparator to used for the sort
  18. */
  19. @SuppressWarnings("fallthrough")
  20. private static <T> void binarySort(T[] a, int lo, int hi, int start,
  21. Comparator<? super T> c) {
  22. assert lo <= start && start <= hi;
  23. if (start == lo)
  24. start++;
  25. for ( ; start < hi; start++) {
  26. T pivot = a[start];
  27.  
  28. // Set left (and right) to the index where a[start] (pivot) belongs
  29. int left = lo;
  30. int right = start;
  31. assert left <= right;
  32. /*
  33. * Invariants:
  34. * pivot >= all in [lo, left).
  35. * pivot < all in [right, start).
  36. */
  37. while (left < right) {
  38. int mid = (left + right) >>> 1;
  39. if (c.compare(pivot, a[mid]) < 0)
  40. right = mid;
  41. else
  42. left = mid + 1;
  43. }
  44. assert left == right;
  45.  
  46. /*
  47. * The invariants still hold: pivot >= all in [lo, left) and
  48. * pivot < all in [left, start), so pivot belongs at left. Note
  49. * that if there are elements equal to pivot, left points to the
  50. * first slot after them -- that's why this sort is stable.
  51. * Slide elements over to make room for pivot.
  52. */
  53. int n = start - left; // The number of elements to move
  54. // Switch is just an optimization for arraycopy in default case
  55. switch (n) {
  56. case 2: a[left + 2] = a[left + 1];
  57. case 1: a[left + 1] = a[left];
  58. break;
  59. default: System.arraycopy(a, left, a, left + 1, n);
  60. }
  61. a[left] = pivot;
  62. }
  63. }

可以看到其实其代码一点也不复杂,与我们上面分析的c语言代码几乎完全相同,只不过它所排序的元素不再是简单的int型,比较规则也不再是简单的比较数的大小,而是通过java中的Comparator接口来规定的,可以看到注释远远多于代码量,一方面这是因为那些天才们用其高超的艺术大大的简化了代码,另一方面也是为了解释关于选择折半插入排序的原因:

  1. /**
  2. * Sorts the specified portion of the specified array using a binary
  3. * insertion sort. This is the best method for sorting small numbers
  4. * of elements. It requires O(n log n) compares, but O(n^2) data
  5. * movement (worst case).
  6.  
  7. /*
  8. * The invariants still hold: pivot >= all in [lo, left) and
  9. * pivot < all in [left, start), so pivot belongs at left. Note
  10. * that if there are elements equal to pivot, left points to the
  11. * first slot after them -- that's why this sort is stable.
  12. * Slide elements over to make room for pivot.
  13. */

从我截取的这两段注释来看,可以知道:

1折半插入排序是最好的算法对于排序小数量的元素This is the best method for sorting small numbers of elements.

2它只需要O(nlogn)的比较次数,但是其移动次数仍然为 O(n^2)。It requires O(n log n) compares, but O(n^2) data movement (worst case).

3它是稳定的排序算法。that's why this sort is stable.而快速排序不是稳定的排序。

分析到这我们就可以知道为何会选择折半插入排序,其中1和3是最主要的原因。

【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法的更多相关文章

  1. 从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射

    从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射.Collection 接口又有 3 ...

  2. 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API

    https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ...

  3. Spring Ioc源码分析系列--Ioc源码入口分析

    Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...

  4. 排序系列 之 折半插入排序算法 —— Java实现

    基本思想: 折半插入算法是对直接插入排序算法的改进,排序原理同直接插入算法: 把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素:排序过程即每次从无序表中 ...

  5. JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

    文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...

  6. 《Java Spring框架》基于IDEA搭建Spring源码

    第一步: IDEA :IntelliJ IDEA 2018.1.4    :JDK安装(必须1.8或者以上),IDEA安装(过程省略). 第二步: Gradle:下载地址:https://servic ...

  7. Java后端框架之Spring Boot详解,文末有Java分布式实战项目视频可取

    在 Java 后端框架繁荣的今天,Spring 框架无疑是最最火热,也是必不可少的开源框架,更是稳坐 Java 后端框架的龙头老大. 用过 Spring 框架的都知道 Spring 能流行是因为它的两 ...

  8. java—三大框架详解,其发展过程及掌握的Java技术慨括

    Struts.Hibernate和Spring是我们Java开发中的常用关键,他们分别针对不同的应用场景给出最合适的解决方案.但你是否知道,这些知名框架最初是怎样产生的? 我们知道,传统的Java W ...

  9. 【原】Spring源码浅析系列-导入源码到Eclipse

    用了Spring几年,平时也断断续续在项目里看过一些源码,大多都是比较模糊的,因为一旦从一个地方进去就找不到方向了,只能知道它大概是做了什么事能达到这个功能或者效果,至于细节一般没有太深入去研究.后来 ...

随机推荐

  1. bzoj4558[JLoi2016]方 容斥+count

    4558: [JLoi2016]方 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 452  Solved: 205[Submit][Status][D ...

  2. CCA更新流程分析

    1 CCA CCA(空间信道评估)在CSMA/CA中比较非常重要,事关整机吞吐量,所以对其实现进行简单分析.CCA好像应该有2种:CCA-CS,是属于PLCP层的,捕获到能量且能量值高于-82dB后, ...

  3. Python IDLE背景主题

    相信刚进入python学习之路的朋友们,都还是挺喜欢python自带的IDLE,但是白的代码背景色以及其它的代码色确实让人看着有点不舒服,所以当时也琢磨着能不能自己给它换换颜色,这个当然可以,废话不多 ...

  4. 数据库的事务、ACID及隔离级别

    事务 所谓事务是用户定义的一个数据库操作序列,这些操作要么全做,要么不做,是一个不可分割的工作单位.例如,在关系数据库中,一条或一组SQL语句.整个程序都可以是一个事务. 事务和程序是两个概念,一个程 ...

  5. 有些时候会看到url参数上出现%BF之类

    这是URLDecoder和URLEncoder的原因 因为他们是参数,避免影响网页的连接跳转,再到了服务器的时候会自动转过来 当URL地址中仅包含普通非中文字符串和application/x-www- ...

  6. Python小代码_12_生成前 n 行杨辉三角

    def demo(t): print([1]) print([1, 1]) line = [1, 1] for i in range(2, t): r = [] for j in range(0, l ...

  7. mongo数据删除和游标

    数据删除 db.集合.remove(删除条件,是否只删除一个数据);默认删多条(false)true删除一条db.集合.remove({}) 删除所有元素但集合还在db.集合.drop() 删除集合 ...

  8. 数据库4m10d作业

    Create table student ( Sno char(15) primary key , Sname varchar(10) not null, Sage tinyint , Special ...

  9. jmeter分布式测试远程连接失败

    jmeter分布式部署其实很简单.但今天测试的时候发现了一个坑,远程连接一直失败. 原因:服务器上部署了slave,而这台服务器上有多个网卡.举个例子:ip分别为:192.168.100.6,10.1 ...

  10. Python3 解释器

    Linux/Unix的系统上,Python解释器通常被安装在 /usr/local/bin/python3.4 这样的有效路径(目录)里. 我们可以将路径 /usr/local/bin 添加到您的Li ...