本文整理来源 《轻松学算法——互联网算法面试宝典》/赵烨 编著

插入排序

什么是插入排序

插入排序分为两种,一种是直接插入排序,一种是二分法插入排序。这两种排序实际上都是插入排序,唯一的不同就是插入的方式不一样。

插入排序就是往数列里面插入数据元素。一般我们认为插入排序就是往一个已经排好顺序的待排序的数列中插入一个数之后,数列依然有序。

二分插入排序应该也是用来分治法的思想去排序的。实际上二分就是使用二分查找来找到这个插入的位置,剩下的插入的思想其实和直接插入排序一样。

所以完成插入排序,就是需要找到这个待插入元素的位置。

插入排序的原理

插入排序实际上把待排序的数列分成了两部分,一部分已排好序,另一部分待排序。

直接插入排序的整个执行过程:

  1. 首先需要明确待排序的数列由两部分组成,一部分是已排好序的部分,另一部分是待排序的部分。
  2. 接着我们每次选择待排序的部分的第1个元素,分别与前面的元素进行比较。当大于前面的元素时,可以直接进入已排序的部分;当小于前面的元素时,需要把这个元素拿出来,将前面的元素后移一位,继续与前面的元素相比,知道比较完数组的第1个元素或者出现一个元素小于我们拿出来的这个元素,这时停止比较、移动,直到把这个元素怒放到当时的空位上。
  3. 一直重复步骤2,当排序的部分已经没有元素可进行插入时,停止操作,当前的数列为已经排好序的数列。

插入排序的实现

首先外层是一个大循环,循环这个待排序的部分数列,内层是分别与前1个元素进行比较、移动,直到找到位置进行插入位置。

public class InsertSort {private int[] array;public InsertSort(int[] array) {this.array = array;}public void sort() {if (array == null) {throw new RuntimeException("array is null");}int length = array.length;if (length > 0) {for (int i = 1; i < length; i++) {int temp = array[i];int j = i;for (; j > 0 && array[j - 1] > temp; j--) {array[j] = array[j - 1];}array[j] = temp;}}}public void print(){for (int anArray : array) {System.out.println(anArray);}}
}

测试代码

public class InsertSortTest {@Testpublic void main(){int[] arrays = {5,9,1,9,5,3,7,6,1};InsertSort insertSort = new InsertSort(arrays);insertSort.sort();insertSort.print();}}

插入排序的特点及性能

插入排序的操作很简单,而且我们通过实例及原理可以知道,插入排序在数列近似有序时,效率会非常高,因为这样会减少比较和移动的次数。

插入排序的时间复杂度是$O({n}^2)$ ,我们会发现这个实现是双重嵌套循环,外层执行n遍,内层在最坏的情况下执行n遍,而且除了比较操作还有移动操作。最好的情况是数列近似有序,这时一部分内层循环只需要比较及移动较少的次数就可以完成排序。如果数列本身已经排好序,那么插入排序也可以达到线性实现复杂度及$O(n^2)$,所以我们应该明确认识到,使用插入排序算法进行排序时,数列越近似有序,性能越高。

插入排序的空间复杂度时$O(1)$,是常量级,由于在采用插入排序时,我们只需要使用一个额外的空间来存储这个"拿出来"的元素,所以插入排序只需要额外的一个空间去做排序,这是常量级的空间消耗。

插入排序时稳定的,由于数组内部自己排序,把后面的部分按前后顺序一点点地比较、移动,可以保持相对顺序不变,所以插入排序是稳定的排序算法。

希尔排序

插入排序算法主要是比较和移动的两个操作,会导致时间复杂度很大。但是插入排序在序列本身有序时能够达到$O(n)$的时间复杂度,也就是说实际上如果序列本身有一定的有序性,那么使用插入排序的效率会更高,如果序列本身很短,那么插入排序的效率会很高。

什么是希尔排序

希尔排序也是一种插入排序算法,也叫做缩小增量排序,是直接插入排序的一种更高效的改进算法。

希尔排序在插入排序的基础上,主要通过两点来改进排序算法:一是在插入排序在对近似有序的数列进行排序时,排序的性能会比较好;二是插入排序的性能比较低效,及每次只能将数据移动一位。

希尔排序的原理

希尔排序的基本思想是:把待排序的数列按照一定的增量分割成多个数列。但是这个子数列不是连续的,二是通过前面提到的增量,按照一定相隔的增量进行分割的,然后对各个子数列进行插入排序,接着增量逐渐减小,然后依然对每部分进行插入排序,在减小到1之后直接使用插入排序处理数列。

特别强调,这里选择增量的要求是每次都要减少,知道最后一次变为1为止。首选增量一般为$\frac{n}{2}$ ,n为待排序的数列长度,并且每次增量都为上次的$\frac{1}{2}$

希尔排序的实现

希尔排序实际上只是插入排序的改进,在算法实现上,我们需要额外操作的只有对增量的处理及对数列的分块处理。

public class ShellSort {private int[] array;public ShellSort(int[] array) {this.array = array;}public void sort() {int temp;for (int k = array.length / 2; k > 0; k /= 2) {for (int i = k; i < array.length; i++) {for (int j = i; j >= k; j -= k) {if (array[j - k] > array[j]) {temp = array[j - k];array[j - k] = array[j];array[j] = temp;}}}}}public void print() {for (int anArray : array) {System.out.println(anArray);}}
}
public class ShellSortTest {@Testpublic void main(){int[] arrays = {5,9,1,9,5,3,7,6,1};ShellSort shellSort= new ShellSort(arrays);shellSort.sort();shellSort.print();}}

希尔排序的特点及性能

其实希尔排序只使用了一种增量的方式去改进插入排序,从上述对该算法的描述及实例中,我们能够清楚的知道实际上希尔排序在内部还是使用插入排序进行处理的。但是这个增量确实有它 意义,不管数列有多长,刚开始时增量会很大,但是数列整体已经开始趋于有序了,所以插入排序的速度还是会越来越快的。

在时间复杂度上,由于增量的序列不一定,所以时间复杂度也不确定。这在数学上还无法给出确切的结果。我们可以采用每次除以2的方式,但是研究,有以下几种推荐序列:

  1. N/3+1,N/3^2+1,N/3^3+1••••••(据说在序列数N<100000时最优)
  2. $2^{k}-1$,$2^{(k-1)}-1$,$2^{k-2}-1$••••••(设k为总趟数)
  3. 其他的还有质数

对于每次除以2的增量选择,希尔排序的最好情况当然是本身有序,每次区分都不用排序,时间复杂度是$O(n)$;但是在最坏的情况下依然每次都需要移动,时间复杂度与直接插入排序在最坏情况下的时间复杂度一样$O(n^{2})$

但是一般认为希尔排序的平均时间复杂度时$O({n}^{1.3})$。当然,希尔排序的时间复杂度与其增量序列有关,一般我们知道希尔排序会比插入排序快一些,这就足够了。

在希尔排序的实现中仍然使用了插入排序,只是进行了分组,并没有使用其他空间,所以希尔排序的空间复杂度同样是$O(1)$,是常量级的。

在希尔排序中会进行分组、排序,所以同样的元素,其相对位置可能会发生变化,这是因为同样值的元素若不在一个组中,则有可能后面的元素会被移动到前面。所以希尔排序是不稳定的算法。

希尔排序的适用场景

在使用希尔排序时,需要选择合适的增量序列作为排序辅助,而这也是一个比较复杂的抉择。所以希尔排序在实际使用中并不常用。

简单选择排序

什么是选择排序

选择排序是一种非常简单的排序算法,就是在序列中依次选择最大(或者最小)的数,并将其放到待排序的数列的起始位置。

简单选择排序的原理

简单选择排序的原理非常简单,即在待排序的数列中寻找最大(或者最小)的一个数,与第1个元素进行交换,接着在剩余的待排序的数列中继续找最大(最小)的一个数,与第2个元素交换。依次类推,一直到待排序的数列中只有一个元素为止。

也就是说,简单选择排序可分为两部分,一部分是选择待排序的数列中最小的一个数,另一部分是让这个数与待排序的数列部分的第1个数进行交换,直到待排序的数列只有一个元素,至此整个数列有序。

简单排序的实现

public class SelectSort {private int[] array;public SelectSort(int[] array) {this.array = array;}public void sort() {int length = array.length;for (int i = 0; i < length; i++) {int minIndex = i;for (int j = i + 1; j < array.length; j++) {if (array[j] < array[minIndex]) {minIndex = j;}}if (minIndex != i) {int temp = array[minIndex];array[minIndex] = array[i];array[i] = temp;}}}public void print() {for (int anArray : array) {System.out.println(anArray);}}
}

测试代码

public class SelectSortTest {@Testpublic void main(){int[] arrays = {5,9,1,9,5,3,7,6,1};SelectSort selectSort = new SelectSort(arrays);selectSort.sort();selectSort.print();}}

选择排序的特点及性能

由于在简单选择排序中,我们一般在原本的待排序的数组上排序并交换,基本上使用的都是常量级的额外空间,所以其空间复杂度时$O(1)$

在最好的情况下,每次都要找的最大(或者最小)的元素就是待排序的数列的第1个元素,也就是说数列本身有序,这样我们只需要一次遍历且不需要交换,即可实现一趟排序;而在最坏的情况下,每次在数列中要找的元素都不是第1个元素,每次都需要交换。比较的次数只与数列的长度有关,而在外部遍历整个数列,也与长度有关,所以这样的双重循环不管在什么情况下,时间复杂度都是$O(n^{2})$,但是由于选择有序不需要一个一个地往前移动,而是直接交换,而比较所消耗的CPU要比交换所消耗的CPU小一些,所以选择排序的时间复杂度相对于冒泡排序会好一些。

简单选择排序优化

通过选择排序的思想,我们知道选择排序的一个重要步骤是在待排序的数列中寻找最大(或者最小)的一个元素,那么如何寻找这个元素就成为一个可以优化的点。

另外,我们每次都要寻找两个值中的一个最大值,一个是最小值。这时如果需要将数列的最后一个元素进行交换。这样我们一次就能寻找两个元素进行交换,把最大值与待排序的数列的最后一个元素进行交换。这样我们一次就能够寻找两个元素,使外层循环的时间缩短了一半,性能也提高了很多。通过一次遍历就可以找到两个最值,并且没有其他性能损耗。

简单选择排序

简单选择排序并不很常见,它只是选择排序的一个思想基础,选择排序还有其他方案可以实现。

小结

类别 排序方法 时间复杂度(平均|最好|最坏) 空间复杂度(辅助存储) 稳定性
插入排序 直接插入 $O(n^2)$ $O(n)$ $O(n^2)$
插入排序 希尔排序 $O(n^{1.3})$ $O(n)$ $O(n^2)$
选择排序 简单选择 $O(n^2)$ $O(n^2)$ $O(n^2)$
选择排序 堆排序 $O(nlogn)$ $O(nlogn)$ $O(nlogn)$
交换排序 冒泡排序 $O(n^2)$ $O(n)$ $O(n^2)$
交换排序 快速排序 $O(nlogn)$ $O(nlogn)$ $O(nlogn)$

一般情况下在选择排序算法时有限选择快速排序,虽然堆排序的空间复杂度更低,但是堆排序没有快速排序简单。

转载于:https://www.cnblogs.com/mr-cc/p/8710333.html

死磕算法第三弹——排序算法(2)相关推荐

  1. 死磕算法第三弹——排序算法(1)

    本文整理来源 <轻松学算法--互联网算法面试宝典>/赵烨 编著 算法基础 对于算法性能分析来说,除了时间复杂度,还是有空间复杂度.稳定性等指标.而我们平时说的算法的复杂度可以分为两个部分: ...

  2. 面试前你必须知道的三个排序算法

    今天分享的是三种排序算法,在面试.实际编程中经常会碰到和使用到的,我会带领大家从分析排序算法技巧上以及代码实现上全面理解这一知识点的掌握. 一.如何分析一个「排序算法」 1. 执行效率 ① 最好.最坏 ...

  3. Java学习 第三章 数组(三)排序算法

    ** Java学习 第三章 数组(三)排序算法 ** 主要内容:排序算法.排序算法横向比较.Arrays工具类的使用.数组常见异常 1.数组中涉及到的常见算法:排序算法 1.1 排序算法分类:内部排序 ...

  4. Java算法--第三章--排序(14)概述

    Java算法–第三章–排序(14)概述 排序算法的总结: 一.基础排序-----算法评估等级:O(n²) 1.冒泡 谁大谁上,每一轮都把最大的顶到天花板效率太低O(n2)–掌握swap 2.选择排序, ...

  5. 排序算法 - 面试中的排序算法总结

    排序算法总结 查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中.因为其实现代码较短,应用较常见.所以在面试中经常会问到排序算法及其相关的问题.但万变不离其宗,只要熟悉了思想,灵活运用也不 ...

  6. 机器学习(二十二)——推荐算法中的常用排序算法, Tri-training

    推荐算法中的常用排序算法 Pointwise方法 Pranking (NIPS 2002), OAP-BPM (EMCL 2003), Ranking with Large Margin Princi ...

  7. 选择排序算法流程图_常用排序算法之选择排序

    前两天给大家分享了冒泡排序和插入排序(没关注的同学,可以关注后查看历史消息),今天继续给大家分享另一种常用的排序算法--选择排序. 选择排序 选择排序和插入排序很相似,也区分已排序区间和未排序区间,选 ...

  8. 【Java数据结构与算法】第六章 算法的时间复杂度、算法的空间复杂度和排序算法的介绍

    第六章 算法的时间复杂度.算法的空间复杂度和排序算法的介绍 文章目录 第六章 算法的时间复杂度.算法的空间复杂度和排序算法的介绍 一.算法的时间复杂度 1.时间频度 2.时间复杂度 3.常见的时间复杂 ...

  9. 《数据结构与算法》实验:排序算法实验比较——选择排序 堆排序

    <数据结构与算法>实验和课程Github资源 <数据结构与算法>实验:线性结构及其应用--算术表达式求值 <数据结构与算法>实验:树型结构的建立与遍历 <数据 ...

最新文章

  1. javascript禁止修改对象
  2. 【算法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细
  3. 利用ISA2006发布Exchange的RPC over HTTPS
  4. Docker Toolbox下配置国内镜像源-阿里云加速器
  5. c语言求信源的信息熵,[转载]关于信息熵及信源熵率
  6. 收藏模板:开发工程师提测申请模板
  7. android中打开地理位置,Android中获取地理位置经纬度
  8. 微信账户显示有异常怎么办?要如何解除?
  9. phython编写图形界面
  10. ACCESS备件库管理数据库
  11. SAP固定资产中的几个日期
  12. linux安装程序出现了问题,linux安装extundelete以及对遇到问题的解决
  13. Mysql导出表结构到excel
  14. 啊哈C——学习3.7一起来找茬
  15. Halcon算子—create_shape_model参数解析及优化
  16. How Intense Are You? Predicting Intensities of Emotions and Sentiments Using Stacked Ensemble
  17. 【生动理解】深度学习中常用的各项评价指标含义TP、FP、TN、FN、Accuracy、Recall、IoU、mIoU
  18. ubuntu上的PDF编辑器--Master pdf editor
  19. 安装zookeeper和hbase
  20. CGLib 动态代理Demo

热门文章

  1. js正则表达式 URL格式匹配 http替换成https
  2. 需要授权的 API ,必须在请求头中使用 Authorization 字段提供 token 令牌
  3. session Bean是什么?
  4. 使用mokey进行压力测试
  5. 化学催化剂行业关键领域及整体市场规模分析
  6. HTC设计出能同时上下滑,和侧滑的Touch手机!用户感觉一定不错
  7. 【力扣时间】【1518】【简单】换酒问题
  8. SCI文章免费下载的网站
  9. 波音737和波音787 分别有多少个作为
  10. BUAA OO 第二单元 电梯