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

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

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

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

程序运行结果如下:

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

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

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

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

/**
* Sorts the specified portion of the specified array using a binary
* insertion sort. This is the best method for sorting small numbers
* of elements. It requires O(n log n) compares, but O(n^2) data
* movement (worst case).
*
* If the initial part of the specified range is already sorted,
* this method can take advantage of it: the method assumes that the
* elements from index {@code lo}, inclusive, to {@code start},
* exclusive are already sorted.
*
* @param a the array in which a range is to be sorted
* @param lo the index of the first element in the range to be sorted
* @param hi the index after the last element in the range to be sorted
* @param start the index of the first element in the range that is
* not already known to be sorted ({@code lo <= start <= hi})
* @param c comparator to used for the sort
*/
@SuppressWarnings("fallthrough")
private static <T> void binarySort(T[] a, int lo, int hi, int start,
Comparator<? super T> c) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
T pivot = a[start]; // Set left (and right) to the index where a[start] (pivot) belongs
int left = lo;
int right = start;
assert left <= right;
/*
* Invariants:
* pivot >= all in [lo, left).
* pivot < all in [right, start).
*/
while (left < right) {
int mid = (left + right) >>> 1;
if (c.compare(pivot, a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
assert left == right; /*
* The invariants still hold: pivot >= all in [lo, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide elements over to make room for pivot.
*/
int n = start - left; // The number of elements to move
// Switch is just an optimization for arraycopy in default case
switch (n) {
case 2: a[left + 2] = a[left + 1];
case 1: a[left + 1] = a[left];
break;
default: System.arraycopy(a, left, a, left + 1, n);
}
a[left] = pivot;
}
}

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

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

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

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. POJ 3415 不小于k的公共子串的个数

    Common Substrings Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 9248   Accepted: 3071 ...

  2. UVA 3713 Astronauts

    The Bandulu Space Agency (BSA) has plans for the following three space missions: • Mission A: Landin ...

  3. 2017ACM/ICPC广西邀请赛-重现赛 1007.Duizi and Shunzi

    Problem Description Nike likes playing cards and makes a problem of it. Now give you n integers, ai( ...

  4. vim的基本介绍

    vim四种模式: 1.正常模式(Normal-mode) 当我们打开vim时就会进入到正常模式,这个时候是不能编辑的,一般用于浏览文件,包括复制,删除一些文本操作.我们可以通过键盘的上下左右来进行光标 ...

  5. sqlserver 按照特定值排序查询结果

    select * from t_ss_student order by case when xm like '林%' then 1 else 2 end asc; 姓林的会排在前面

  6. java底层学习

    额,马上就要面试了,Java的底层肯定是需要了解的.网上找了找java的底层文章,做个记号.java底层主要是类的加载.连接和初始化. 本文主要分为四个方面: (1)java底层概述 (2)new和n ...

  7. JS基础(二)

    一.JS中的循环结构 循环结构的执行步骤 1.声明循环变量: 2.判断循环条件: 3.执行循环体操作: 4.更新循环变量 然后,循环执行2-4,直到条件不成立时,跳出循环. while循环()中的表达 ...

  8. redis的基本数据类型

    一:redis是一个开源的,使用C语言编写,支持网络,可基于内存亦可持久化的日志型,key-value方式存储的nosql数据库.作为缓存服务器,速度效率都很快,和memcache相似 redis支持 ...

  9. 深入理解SpringCloud之引导程序应用上下文

    tips:我希望通过这篇文章来给对于bootstrap还不理解的朋友带来帮助.当然这篇文章不仅仅是讲解知识,我更希望给广大朋友带来学习与理解官方文档的一种思路.阅读本文前,建议大家对SpringBoo ...

  10. blog写作心得体会

    虽然写blog也挺久了,写出来的东西自己回顾的时候也会怀疑读者是否能看的明白,还是有种流水账的感觉,以后希望多从读者的角度出发.下面记录一些以后写博客的注意点. 具体关于某种技术点的小知识还有碰到的各 ...