插入排序是常见的内部排序之一。常见的插入排序包括直接插入排序、Shell排序、折半排序。本篇主要介绍这三个排序。

  转载请注明出处——http://www.cnblogs.com/zrtqsk/p/3807611.html,谢谢!

一、直接插入排序

  直接插入排序大概是我们最容易理解的一类排序了。

  1、原理

  对于n个元素的记录。

  第一趟  :  把第2个元素拿出来跟第1个元素对比,小的在前面、大的在后面。

  第二趟  :  把第3个元素拿出来插入到前2个元素中,使他们有序。

  第三趟  :  把第4个元素拿出来插入到前3个元素中,使他们有序。

  ......

  第n-1趟 :  把第n个元素拿出来插入到前n-1个元素中,排序完成。

  2、Java实现

package sort;

/**
* 想法:如果要将数组集体后移,那么必须要从后往前遍历。
*
* @ClassName: InserSort
* @Description: 插入排序
* @author qsk
* @date 2014年6月21日 下午4:06:04
*/
public class InsertSort { public static void sort(int[] source) {
SortUtil.outputArray(source);
int size = source.length;
// 从第二个开始,遍历每一个数组元素
for (int i = 1; i < size; i++) {
// 取出来
int temp = source[i];
// 跟之前排序好的进行比较、插入
for (int j = 0; j < i; j++) {
// 如果比某一个小,
if (temp < source[j]) {
// 那么原排序好的,集体后移
for (int k = i; k > j; k--) {
source[k] = source[k - 1];
}
source[j] = temp;
//输出
SortUtil.outputArray(source);
// 集体后移后,跳出循环
break;
}
}
}
} // 改进后的
public static void sort1(int[] source) {
SortUtil.outputArray(source);
int size = source.length;
// 从第二个开始,遍历每一个数组元素
for (int i = 1; i < size; i++) {
// 取出来
int temp = source[i];
// 从后往前遍历,找到插入位置
int j;
for (j = i - 1; j >= 0 && temp < source[j]; j--) { source[j + 1] = source[j];
}
// 由于上面的循环完毕之后执行了j--,所以这里给source[j+1]赋值
source[j + 1] = temp;
//输出
SortUtil.outputArray(source);
}
} public static void main(String[] args) {
sort1(SortUtil.getRandomArray());
}
}

如上,有2个实现,sort()是我很快写出来的,很明显,3个嵌套循环非常麻烦。这里我们可以发现,遍历一个数组结构的时候,向前和向后遍历都很讲究,要想清楚处理逻辑再决定选择向前还是向后遍历。注释解释的很清楚了,不必多说。结果如下:

[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 64, 71, 33, 57, 38, 30, 42, 14]
[6, 22, 33, 64, 71, 57, 38, 30, 42, 14]
[6, 22, 33, 57, 64, 71, 38, 30, 42, 14]
[6, 22, 33, 38, 57, 64, 71, 30, 42, 14]
[6, 22, 30, 33, 38, 57, 64, 71, 42, 14]
[6, 22, 30, 33, 38, 42, 57, 64, 71, 14]
[6, 14, 22, 30, 33, 38, 42, 57, 64, 71]

  3、时间复杂度和稳定性
  直接插入排序的时间复杂度是O(N2)
  假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1!因此,直接插入排序的时间复杂度是O(N2)。
  直接插入排序是稳定的算法,它满足稳定算法的定义。

二、折半插入排序

  1、原理

  折半插入排序是对直接插入排序的改进。

  我们看直接插入排序的步骤简单而言其实就2步,第1步是从已经排好序的数组中找到该插入的点,第2步是将数据插入,然后后面的数据整体后移。那么直接插入排序是如何找到该插入的点的呢?是无脑式的从头到尾的遍历。问题是被插入的数组是排好序的,根本没有必要从头到尾遍历。折半插入排序就是改进了第1步——从已经排好序的数组中找到该插入的点。

  折半插入排序是怎么做的呢?非常简单。取已经排好序的数组的中间元素,与插入的数据进行比较,如果比插入的数据大,那么插入的数据肯定属于前半部分,否则属于后半部分。这样,不断遍历缩小范围,很快就能确定需要插入的位置。这就是所谓“折半”。

  (Arrays类的binarySearch()方法就是折半查找的实现)

  

  2、Java实现

package sort;

public class HalfInsertSort {

    public static void sort(int[] source) {
int size = source.length;
for (int i = 1; i < size; i++) {
// 拿出来
int temp = source[i];
int begin = 0; // 标记排好序的数组的头部
int end = i - 1; // 标记排好序数组的尾部
// 只要头部一直小于尾部,说明temp还在2个标记范围内
while (begin <= end) {
// 取2个标记的中间数据的值
int mid = (begin + end) / 2;
// 比较,若比中间值大,则范围缩小一半
if (temp > source[mid]) {
begin = mid + 1;
// 否则,范围也是缩小一半
} else {
end = mid - 1;
}
// 循环结束时,end<begin,即i应该插入到begin所在的索引
}
// 从begin到i,集体后移
for (int j = i; j > begin; j--) {
source[j] = source[j - 1];
}
// 插入i
source[begin] = temp;
SortUtil.outputArray(source);
}
} public static void main(String[] args) {
sort(SortUtil.getRandomArray());
}
}

如上,注释已经非常清楚了。结果如下:

[4, 11, 4, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 81, 83, 86, 35, 90]
[4, 4, 11, 35, 41, 61, 81, 83, 86, 90]
[4, 4, 11, 35, 41, 61, 81, 83, 86, 90]

  3、时间复杂度和稳定性
  折半插入排序的时间复杂度是O(N2)
  折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。
  折半插入排序是稳定的算法,它满足稳定算法的定义。

三、Shell排序

  1、原理

  Shell排序也是对直接插入排序的改进。它实质上是一种分组插入方法。可以这么简单理解:

  对于n个元素的数组,假设增量为 h:

  第一趟  :  从第1个元素开始,每隔h取一个元素,那么最后可以得到n/h个元素,一边取,一边通过直接插入将这h个元素排序

  第二趟  :  从第2个元素开始,每隔h取一个元素,跟第一趟一样。  

  ...

  第h趟   :  从第h个元素开始,每隔h取一个元素,跟第一趟一样。

  (此时,整个数组还不是有序的)

  然后,减少h的值,重复上面的操作,直到h减小为1,排序完成。

  2、Java实现

package sort;

/**
* @ClassName: ShellSort
* @Description: 折半排序
* @author qsk
* @date 2014年6月22日 下午3:48:01
*/
public class ShellSort { public static void sort(int[] source) {
// 排序前先输出
SortUtil.outputArray(source);
int size = source.length;
// 增量
int h = 1;
// 得到增量的最大值
while (h <= size / 3) {
h = h * 3 + 1;
}
while (h > 0) {
System.out.println("h的值为" + h);
// 因为每个i都要跟i-h比较,所以从h到size遍历了每个数组元素
for (int i = h; i < size; i++) {
// 取值
int temp = source[i];
// 取i之前h距离的索引为j
int j = i - h;
// 如果temp比j对应的值小
if (temp < source[j]) {
// 从j开始往前每隔h取一个值,如果这个值比temp要大,那么把这个值后移h个单位。
for (; j >= 0 && source[j] > temp; j -= h) {
source[j + h] = source[j];
}
// 最后将temp的值插入合适位置
source[j + h] = temp;
SortUtil.outputArray(source);
} }
h = (h - 1) / 3;
}
} public static void sort1(int[] source) {
// 排序前先输出
SortUtil.outputArray(source);
int size = source.length;
// 增量
int h = 1;
// 得到增量的最大值
while (h <= size / 3) {
h = h * 3 + 1;
}
while (h > 0) {
System.out.println("h的值是" + h);
// 0到h的遍历
for (int x = 0; x < h; x++) {
// i每次递增h,这两个for循环,遍历了所有数组元素
for (int i = x + h; i < source.length; i = i + h) {
// 用temp记录i的值
int temp = source[i];
int j;
// 从j开始往前,每隔h取一个值与temp进行比较,若比temp大则向后移动h个单位
for (j = i - h; j >= 0 && source[j] > temp; j = j - h) {
source[j + h] = source[j];
}
source[j + h] = temp;
}
// 每一趟排序后输出
SortUtil.outputArray(source);
}
h = (h - 1) / 3;
}
} public static void main(String[] args) {
sort1(SortUtil.getRandomArray());
}
}

这里有2个算法实现,第二个sort1()方法,用了3个for循环嵌套,比较容易理解,不过实在不够优雅。而sort1()将其进行了改进,使用2个for循环实现。

我们知道,Shell排序的关键是确定增量 h 的值,以及 h 如何减少。上文的 h 值算法由Knuth提出,是比较常用的取h值的算法。经常可以看到许多人实现shell排序,取h的时候,直接减半,这样,数组项移动的距离很长,不过移动元素的个数较少,相对而言没有Knuth的算法有效率。

上面的结果如下:

h的值是4
[4, 9, 89, 85, 36, 5, 85, 44, 96, 96]
[4, 5, 89, 85, 36, 9, 85, 44, 96, 96]
[4, 5, 85, 85, 36, 9, 89, 44, 96, 96]
[4, 5, 85, 44, 36, 9, 89, 85, 96, 96]
h的值是1
[4, 5, 9, 36, 44, 85, 85, 89, 96, 96]

  3、时间复杂度和稳定性
  Shell排序的时间复杂度是根据增量h的不同而不同,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²)。Shell排序的时间复杂度在O(n3/2)-O(n7/6)之间。
  Shell排序算法是一种不稳定的排序算法。

参考:《Java程序员的基本修养》

  http://www.cnblogs.com/skywang12345/p/3597597.html

基本排序(二)插入排序(直接插入、Shell、折半)的更多相关文章

  1. 插入排序(直接插入、折半、Shell)

    直接插入排序(顺序插入排序) 基本思想: 排序过程,整个排序过程为n-1趟插入,即先将序列中的第1个元素看成是一个有序子序列,然后从第2个元素开始,逐个进行插入,直至整个序列有序. 在有序序列中插入一 ...

  2. 【排序算法】——冒泡排序、选择排序、插入排序、Shell排序等排序原理及Java实现

    排序 1.定义: 所谓排序,即是整理文件中的内容,使其按照关键字递增或递减的顺序进行排列. 输入:n个记录,n1,n2--,其对应1的关键字为k1,k2-- 输出:n(i1),n(i2)--,使得k( ...

  3. 排序算法三:Shell插入排序

    排序算法三:Shell插入排序 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 引言 在我的博文<"主宰世界"的10种算法短评> ...

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

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

  5. 八大排序方法汇总(选择排序,插入排序-简单插入排序、shell排序,交换排序-冒泡排序、快速排序、堆排序,归并排序,计数排序)

    2013-08-22 14:55:33 八大排序方法汇总(选择排序-简单选择排序.堆排序,插入排序-简单插入排序.shell排序,交换排序-冒泡排序.快速排序,归并排序,计数排序). 插入排序还可以和 ...

  6. Java排序算法分析与实现:快排、冒泡排序、选择排序、插入排序、归并排序(二)

    一.概述: 上篇博客介绍了常见简单算法:冒泡排序.选择排序和插入排序.本文介绍高级排序算法:快速排序和归并排序.在开始介绍算法之前,首先介绍高级算法所需要的基础知识:划分.递归,并顺带介绍二分查找算法 ...

  7. 直接插入排序、折半插入排序、shell插入排序

    直接插入排序:   折半插入排序:   shell插入排序:  

  8. 经典排序算法 – 插入排序Insertion sort

    经典排序算法 – 插入排序Insertion sort  插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕. 插入排序方法分直接插入排序和折半插入排序两种, ...

  9. JavaScript算法(冒泡排序、选择排序与插入排序)

    冒泡排序.选择排序与插入排序复杂度都是二次方级别的,放在一起说吧. 介绍一些学习这三个排序方法的比较好的资料.冒泡排序看<学习JavaScript数据结构与算法>介绍的冒泡排序,选择排序看 ...

随机推荐

  1. 十二种获取Spring的上下文环境ApplicationContext的方法

    转载:https://my.oschina.net/u/2391658/blog/729414

  2. Introduction to Microsoft Dynamics 365 licensing

    Microsoft Dynamics 365 will be released on November 1. In preparation for that, Scott Guthrie hosted ...

  3. JavaScript单元测试框架JsUnit基本介绍和使用

    JavaScript单元测试框架JsUnit基本介绍和使用 XUnit framework XUnit是一套标准化的独立于语言的概念和结构集合,用于编写和运行单元测试(Unit tests). 每一个 ...

  4. PHP(第一天)

    <?php // $name='lisi'; // $age =18; //$bol =true; //$bol =false; // echo ($bol); //echo ('name is ...

  5. IOS开发之学习《AV Foundation 开发秘籍》

    敲了这么久的代码,查阅了很多资料,都是网络电子版的,而且时间久了眼睛也累了,还不如看一下纸质的书籍,让眼睛休息休息. 本篇开始学习<AV Foundation 开发秘籍>,并记录对自己本人 ...

  6. css解决方案

    1,Flexbox(更优雅的布局) ①居中:{display:flex; justify-content:center; align-items:center;}②设定flex-grow属性的话,会自 ...

  7. jacascript中的原型链以原型

    今地铁上看慕课网js课程,又学习到关于原型的一些知识,记录如下.如有偏差欢迎指正: 三张图要连起来看哦~ 图解: 1.创建一个函数foo. 2.运用函数的prototype属性(这个属性就是实例对象的 ...

  8. CouchDB简介

    类型:开源数据库,Apache项目 存储格式:JSON 查询语言:JavaScript API :MapReduce.HTTP 特点 MVCC(Multiversion concurrency con ...

  9. 72个可交付成果(PMBOK2008)

    成果名称 包括内容 来自 用于 事业环境因素 组织文化.政府法规.行业标准.市场条件.工作授权系统.商业数据库.项目管理信息系统 外部现有的 启动.规划.执行过程的输入 组织过程资产 流程与程序(模板 ...

  10. SQL Server删除distribution数据库二

    以前总结过一遍博文SQL Server删除distribution数据库,里面介绍了如何删除distribution数据库.今天介绍一个删除distribution的特殊案例, 在这之前,我不知道这个 ...