【Java】插入排序、希尔排序详解
文章目录
- 1️⃣必备排序常识
- 2️⃣插入排序
- 1.直接插入排序
- 2.优化后的插入排序
- 3.折半插入排序
- 4.性能比较
- 3️⃣希尔排序
- 性能比较
1️⃣必备排序常识
稳定性:在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求能在内存和硬盘(外部存储器)之间移动数据的排序。
时间复杂度:一个排序算法在执行过程中所耗费的时间量级的度量。
空间复杂度:一个排序算法在运行过程中临时占用存储空间大小的度量。
本次讲解的排序都是内排序
2️⃣插入排序
1.直接插入排序
排序原理:
将集合分为两个区间,下标i
代表当前遍历的未排序的元素,已经排序的区间[0...i)
,待排序区间[i...n]
。
每次从待排序区间中间第一个元素“插入”到已排序区间的合适位置,该位置后的已排序元素都往后移动一位,直到整个数组有序。
过程展示:
代码实现:
public static void insertSort(int[] arr){// 已排序区间[0,i)// 待排序区间[i...n]for (int i = 0; i < arr.length - 1; i++) {// 待排序区间的第一个元素arr[i]// 从待排序区间的第一个元素向前看,找到合适的插入位置for (int j = i ; j >= 0 && arr[j + 1] < arr[j]; j--) {swap(arr,j,j + 1);}}
}
private static void swap ( int[] arr, int i, int j){int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}
特性总结:
- 如果当待排序的元素
>
有序区间的最后一个元素时,说明该元素有序区间和待排序元素都有序了,就不需要将该元素再排序,就可以提前结束内层循环。所以元素集合越接近有序,直接插入排序算法的时间效率越高。极端情况下,当集合是一个完全有序的集合,插入排序内层循环一次都不会进入。
所以插入排序经常作为高阶排序算法的优化手段之一(在小区间上接近有序的时候使用)。 - 时间复杂度:
O(N) ~ O(N^2)
。最好的情况是刚好是升序或降序(即和算法的排序顺序一致的时候),只遍历一遍;最坏的情况为刚好有序的但是和算法的顺序刚好相反,此时时间复杂度为n * (n-1) * ··· *2 * 1
为等差数列,按公式计算得为O(N^2)
。所以在数据大部分有序的情况下使用插入排序还是不错的。 - 空间复杂度:
O(1)
,它是一种稳定的排序算法 - 稳定性:稳定
2.优化后的插入排序
上一种排序方法,在进行排序的时候会不断调用swap
方法来将两个元素的位置交换,其实不用每次比较交换,可以直接找到合适的位置,将后面的元素挨个往后移动,将该元素放在位置上集合。
这样排序的运行时间可以大大加快,因为不断调用函数,创建临时变量是一个比较耗时的操作。使用优化后的插入排序可以减少这些操作,后面会给出排序的性能比较。
代码实现:
//不交换元素,直接存放
public static void insertSortOp(int[] arr){for (int i = 0; i < arr.length - 1; i++) {//比较如果不需要插入则直接进入下次循环if(arr[i] > arr[i + 1]){int j = i;//将需要插入的元素保存int tmp = arr[i + 1];//将该位置和后面已经排好序的元素一次往后移动for (; j >= 0 && arr[j] > tmp; j--) {arr[j + 1] = arr[j];}//将该元素放在该位置上arr[j + 1] = tmp;}}
}
3.折半插入排序
因为在插入排序中,每次都是在有序区间中选择插入位置,因此可以使用二分查找来定位元素的插入位置。
代码实现:
//插入排序二分搜索优化版本
public static void insertSortBS(int[] arr){// 有序区间[0..i]// 无序区间(i...n]for (int i = 0; i < arr.length - 1; i++) {if(arr[i + 1] < arr[i]){int left = 0;int right = i;int val = arr[i + 1];while(left <= right){int mid = left + ((right - left) >> 1);if(arr[mid] <= val)left = mid + 1;elseright = mid - 1;}// 搬移left..i的元素for (int j = i; j >= left; j--) {arr[j + 1] = arr[j];}// left就是val插入的位置arr[left] = val;}}
}
4.性能比较
这里给出测试使用的类:
/*** 排序的辅助类* 生成测试数组以及对排序算法进行测试**/
public class SortHelper {// 获取随机数的对象private static final ThreadLocalRandom random = ThreadLocalRandom.current();//在[left...right]上生成n个随机数public static int[] generateRandomArray(int n,int left,int right) {int[] arr = new int[n];for (int i = 0; i < arr.length; i++) {arr[i] = random.nextInt(left,right);}return arr;}/*** 生成一个大小为n的近乎有序的数组* @param n* @param times 交换的次数,次数越小越有序,次数越大越无序* @return*/public static int[] generateSoredArray(int n,int times) {int[] arr = new int[n];for (int i = 0; i < n; i++) {arr[i] = i;}// 交换部分元素,交换次数越小,越有序for (int i = 0; i < times; i++) {// 生成一个在[0..n]上的随机数int a = random.nextInt(n);int b = random.nextInt(n);int temp = arr[a];arr[a] = arr[b];arr[b] = temp;}return arr;}// 根据传入的方法名称就能调用这个方法,需要借助反射// 根据方法名称调用相应的排序方法对arr数组进行排序操作public static void testSort(String sortName,int[] arr) {Class<SevenSort> cls = SevenSort.class;try {Method method = cls.getDeclaredMethod(sortName,int[].class);long start = System.nanoTime();method.invoke(null,arr);long end = System.nanoTime();if (isSorted(arr)) {// 算法正确System.out.println(sortName + "排序结束,共耗时:" + (end - start) / 1000000.0 + "ms");}} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}}// 生成一个arr的深拷贝数组// 为了测试不同排序算法的性能,需要在相同的数据集上进行测试public static int[] arrCopy(int[] arr) {return Arrays.copyOf(arr,arr.length);}public static boolean isSorted(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {if (arr[i] > arr[i + 1]) {System.err.println("sort error");return false;}}return true;}
}
排序十万个随机数时:
public static void main(String[] args) {int n = 100000;int[] arr = SortHelper.generateRandomArray(n, 0, Integer.MAX_VALUE);int[] arrCopy1 = SortHelper.arrCopy(arr);int[] arrCopy2 = SortHelper.arrCopy(arr);SortHelper.testSort("insertSort", arrCopy);SortHelper.testSort("insertSortOp", arrCopy1);SortHelper.testSort("insertSortBS", arrCopy2);
}
运行结果:
可以看出在十万个随机数下,经过优化的插入排序和二分搜索的性能都有很大提升。
排序十万个接近有序数组时:
int n = 100000;
//将有序的数组元素进行随机交换100次就可构成一个接近有序的数组
int[] arr = SortHelper.generateSoredArray(n,100);
运行结果:
可以看出在十万个接近有序的数组下,插入排序的性能是很不错的,接近
O(n)
的时间复杂度。
3️⃣希尔排序
排序原理:
希尔排序法(Shell Sort)又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap
,把待排序数据按照距离为gap
分成同多组,并对每一组内的数据进行排序。然后,逐渐将gap
减小,即每组数据的间距减小,重复上述分组和排序的工作。当gap=1
时,此时就是插入排序了,而且经过前面的排序,整个数组已经被调整得接近有序,使用插入排序就可以较快排好序。
过程展示:
代码实现:
public static void shellSort(int[] arr){int gap = arr.length >> 1;//最后一次循环的gap为1,也就是插入排序了while(gap >= 1){shellSortHelper(arr,gap);//减小间距gapgap >>= 1;}
}private static void shellSortHelper(int[] arr, int gap) {//可以看出这个结构就是插入排序,只是比较元素的间隔变化而已for (int i = 0; i < arr.length - gap; i++) {for (int j = i; j >= 0 && arr[j + gap] < arr[j]; j-=gap) {swap(arr,j,j+gap);}}
}
特性总结:
- 希尔排序是对直接插入排序的优化。
- 当
gap > 1
时都是预排序,目的是让数组更接近于有序,不要小看这些预排序,可以让插入排序的性能有很大的提升。当gap == 1
时,数组已经接近有序的了,这样排序起来就会很快。这样整体而言,可以达到优化的效果。后面会进行性能的测试。 - 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度:
O(n*logn)~O(n^2)
,当间隔gap
取得好的条件下可以达到最好情况,最不理想的情况是当gap == 1
的时候,也就是直接插入排序。 - 稳定性:不稳定。当相同的值被分到不同的组中在进行排序,不同的组中数值大小不确定,此时就不能保证这些数还能保持先后顺序。
性能比较
排序十万个随机数时:
运行结果:
可见此时得益于希尔排序优秀的时间复杂度,快了插入排序将近百倍。希尔排序循环大概进行的次数为:
O(n*logn)
约等于170万。
而直接插入排序接近最坏的情况,O(n^2)
后的计算结果为100亿次。由此可见两种排序在这种情况下根本不是一个数量级的。
排序十万个接近有序数组时:
运行结果:
在接近有序的数组,插入排序接近
O(n)
的时间复杂度和希尔排序接近nlog(n)
的时间复杂度下的排序性能都是很不错的。
在这场比赛中希尔排序胜出。
【Java】插入排序、希尔排序详解相关推荐
- 希尔排序基础java代码_java 算法之希尔排序详解及实现代码
摘要:这篇Java开发技术栏目下的"java 算法之希尔排序详解及实现代码",介绍的技术点是"希尔排序详解.实现代码.希尔排序.Java.实现.代码",希望对大 ...
- Java 插入排序 希尔排序
目录 一.插入排序 二.希尔排序 一.插入排序 public class test {public static void main(String[] args) {int[] arr = {100, ...
- 十大排序详解(java实现)
十大排序详解(java实现) 一.十大排序算法概述 1.定义 2.分类 3.比较 4.相关概念 二.各算法原理及实现 1.冒泡排序 2.简单选择排序(Selection Sort) 3.直接插入排序( ...
- 20191007:选择排序,插入排序,冒泡排序详解
选择排序,插入排序,冒泡排序详解 描述 图例 代码实现 描述 选择排序:将要排序的对象分作两部份,一个是已排序的,一个是未排序的,从后端未排序部份选择一个最小值,并放入前端已排序部份的最后一个. 插入 ...
- c语言排序常用问题,【更新中】【排序详解】解决排序问题(以C语言为例)
[更新中][排序详解]解决排序问题(以C语言为例) [更新中][排序详解]解决排序问题(以C语言为例) 文章目录 排序的相关概念 简单排序 一.插入排序: (一)插入排序基本思想 (二)插入排序基本操 ...
- Sorting 排序详解(c语言实现)
Sorting 排序详解(c语言实现)# 今日突然有任务,明天补充完整. 邮箱:Is_Dmy@163.com期待交流. Hello,各位小伙伴~我是你们的课代表橙橙,今天呢我要给大家分享的是关于内排序 ...
- Java 8 Stream API详解--转
原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java 8引入 ...
- java定时任务框架elasticjob详解
这篇文章主要介绍了java定时任务框架elasticjob详解,Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架.该项目基于成熟的开源产品Quartz和Zo ...
- 蘑菇街2015校招 Java研发笔试题 详解,2015java
蘑菇街2015校招 Java研发笔试题 详解,2015java 1. 对进程和线程描述正确的是( ) A. 父进程里的所有线程共享相同的地址空间,父进程的所有子进程共享相同的地址空间. B. 改变 ...
最新文章
- 【转】Extending Lync Server routing with MSPL
- 个人代码库の全局快捷键
- Redis基础1(定义及基础)
- 记录‘launch_simulation‘failed due to earlier errors的一个解决方法
- 程序员都在用的电脑小技巧,一遍就学会,每天早下班一小时
- 虚拟机修改默认SSH端口号为10022
- 基于云上 Arm 架构赋能数值天气预报
- MATLAB混度系统仿真其二:蔡氏电路系统和三阶RC梯形移相振荡器仿真
- 公积金贷款额度根据什么而定
- No resource found that matches the given name 'Theme.AppCompat.Light.DarkActionBar'
- visual studio 批量注释与取消批量注释快捷键
- Vue 实现 Hover 功能( mouseover 与 mouseenter 的区别)
- 华为云云筑·开发者年度盛典精彩回顾
- Keras学习| ImageDataGenerator的参数
- c++内存管理3: new handler、=default、=delete函数
- 跨平台2D游戏引擎CloudBox
- ogre 开发范例大全(2)
- 无畏谣言,中科灵芝孢子油将科学进行到底!
- DotnetCore学习笔记之IWebHostBuilder(Web主机)
- 改革春风出满地= =#
热门文章
- .cast( )函数的使用
- 单选框(必选)功能的实现
- 【算法】时间和空间复杂度
- 【考研高数-高等数学-基础】第二章 导数与微分
- 绝缘电阻仪测试仪与绝缘耐压测试仪的区别
- Self6D: Self-Supervised Monocular 6D Object Pose Estimation论文翻译
- 互联网金融的信息安全(一)新环境的安全形势
- 微指创始人任春雷携好机友踏入微商领域
- Netty应用:快速了解http各版本的特性 HttpServer的小demo
- 在树莓派上用python实现人脸识别(face_recognition,PIL,opencv)