很早以前曾写过冒泡排序的文章 ,那时候还只知道用排序,现在随着业务的积累,自己在这个行业也懂了一点皮毛,特地总结一下自己学过的排序算法,以及排序的性能,参考了许许多多的资料 ,明白了排序的的方式千奇百怪,我们只能选择最符合我们业务的哪一种,
好了废话不多说了!

1、排序算法介绍

1.1、排序算法的简介

  • 排序也称排序算法(Sort Algorithm), 排序是将一组数据, 依指定的顺序进行排列的过程。

1.2、排序算法的分类

  • 内部排序:指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
  • 外部排序法:数据量过大, 无法全部加载到内存中, 需要借助外部存储(文件等)进行排序。
  • 常见的排序算法分类

2、算法的复杂度

2.1、时间复杂度的度量方法

  • 事后统计的方法:这种方法可行, 但是有两个问题:

    • 一是要想对设计的算法的运行性能进行评测, 需要实际运行该程序;
    • 二是所得时间的统计量依赖于计算机的硬件、 软件等环境因素, 这种方式, 要在同一台计算机的相同状态下运行, 才能比较哪个算法速度更快。
  • 事前估算的方法:通过分析某个算法的时间复杂度来判断哪个算法更优

2.2、时间频度

  • 基本介绍时间频度: 一个算法花费的时间与算法中语句的执行次数成正比例, 哪个算法中语句执行次数多, 它花费时间就多。 一个算法中的语句执行次数称为语句频度或时间频度。 记为 T(n)。 [举例说明]
  • 举例说明-基本案例:比如计算 1-100 所有数字之和,我们设计两种算法:

  • 举例说明-忽略常数项:

    • 2n+20 和 2n 随着 n 变大, 执行曲线无限接近, 20 可以忽略
    • 3n+10 和 3n 随着 n 变大, 执行曲线无限接近, 10 可以忽略

  • 举例说明-忽略低次项:

    • 2n^2+3n+10 和 2n^2 随着 n 变大, 执行曲线无限接近, 可以忽略 3n+10
    • n^2+5n+20 和 n^2 随着 n 变大,执行曲线无限接近, 可以忽略 5n+20

  • 举例说明-忽略系数:

    • 随着 n 值变大, 5n^2+7n 和 3n^2 + 2n , 执行曲线重合, 说明 这种情况下, 5 和 3 可以忽略。
    • 而 n^3+5n 和 6n^3+4n , 执行曲线分离, 说明多少次方式关键

2.3、时间复杂度

  • 一般情况下, 算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数, 用 T(n)表示, 若有某个辅助函数 f(n), 使得当 n 趋近于无穷大时, T(n) / f(n) 的极限值为不等于零的常数, 则称 f(n)是 T(n)的同数量级函数。记作 T(n)=O ( f(n) ), 称O ( f(n) ) 为算法的渐进时间复杂度, 简称时间复杂度。
  • T(n) 不同, 但时间复杂度可能相同。 如: T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的 T(n) 不同, 但时间复杂度相同, 都为 O(n²)。
  • 计算时间复杂度的方法:
    • 用常数 1 代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1
    • 修改后的运行次数函数中, 只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²
    • 去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)

2.4、常见的时间复杂度

2.4.1、常见时间复杂度概述

  • 常见时间复杂度

    • 常数阶 O(1)
    • 对数阶 O(log2n)
    • 线性阶 O(n)
    • 线性对数阶 O(nlog2n)
    • 平方阶 O(n^2)
    • 立方阶 O(n^3)
    • k 次方阶 O(n^k)
    • 指数阶 O(2^n)
  • 结论:
    • 常见的算法时间复杂度由小到大依次为: Ο (1)<Ο (log2n)<Ο (n)<Ο (nlog2n)<Ο (n2)<Ο (n3)< Ο (nk) < Ο (2n) , 随着问题规模 n 的不断增大, 上述时间复杂度不断增大, 算法的执行效率越低
    • 从图中可见, 我们应该尽可能避免使用指数阶的算法

2.4.2、常数阶 O(1)

  • 无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
  • 代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。

2.4.3、对数阶 O(log2n)

2.4.4、线性阶 O(n)

  • 说明:这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度

2.4.5、线性对数阶 O(nlogN)

  • 说明:线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)

2.4.6、平方阶 O(n²)

  • 说明:平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(nn),即 O(n²) 如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(mn)

2.4.7、其他阶

  • 立方阶 O(n³)、 K 次方阶 O(n^k)
  • 说明: 参考上面的 O(n²) 去理解就好了, O(n³)相当于三层 n 循环, 其它的类似

2.5、平均和最坏时间复杂度

  • 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下, 该算法的运行时间。
  • 最坏情况下的时间复杂度称最坏时间复杂度。 一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是: 最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限, 这就保证了算法的运行时间不会比最坏情况更长。
  • 平均时间复杂度和最坏时间复杂度是否一致, 和算法有关(如图)。

2.6、算法的空间复杂度

  • 类似于时间复杂度的讨论, 一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间, 它也是问题规模 n 的函数。
  • 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。 有的算法需要占用的临时工作单元数与解决问题的规模 n 有关, 它随着 n 的增大而增大, 当 n 较大时, 将占用较多的存储单元, 例如快速排序和归并排序算法, 基数排序就属于这种情况
  • 在做算法分析时, 主要讨论的是时间复杂度。 从用户使用体验上看, 更看重的程序执行的速度。 一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间

3、冒泡排序

3.1、基本介绍

  • 冒泡排序(Bubble Sorting) 的基本思想是: 通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值, 若发现逆序则交换, 使值较大的元素逐渐从前移向后部, 就象水底下的气泡一样逐渐向上冒。
  • 优化:因为排序的过程中, 各元素不断接近自己的位置, 如果一趟比较下来没有进行过交换, 就说明序列有序, 因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。 从而减少不必要的比较。 (这里说的优化, 可以在冒泡排序写好后, 再进行)

3.2、冒泡排序图解

  • 第一趟:

    • 从数组 arr 第一个元素开始,与其后面一个元素比较大小
    • 如果 arr[i] > arr[i+1] ,则交换,将大的元素换到后面去
    • 由于是当前元素与其后面一个元素比较大小,所以只需要执行 arr.length - 1 次循环
  • 第二趟:
    • 从数组 arr 第一个元素开始,与其后面一个元素比较大小
    • 由于第一趟排序完成,数组最后一个元素已是最大元素,所以只需要执行 arr.length - 1 - 1 次循环
  • 啥时候完成?下面两个条件满足任意一个即可:
    • 当其中有一趟排序没有元素交换位置时,说明数组已经有序
    • 或:按照上述流程,跑完第 arr.length - 1 趟之后
      • 这样来想:5 个元素的数组,最多只需要跑 4 趟
      • 为什么最多只需要跑 4 趟?因为跑完 4 趟之后,数组第二个元素已经成为了数组第二小的元素,那么数组自然就是有序数组
      • 即数组长度如果为 n ,那么则需要跑 n - 1 趟
  • 总结:两层 for 循环
    • 第一层 for 循环控制走多少趟:for (int i = 0; i < arr.length - 1; i++) {
    • 第二层 for 循环实现针对该趟循环,进行冒泡:for (int j = 0; j < arr.length - 1 - i; j++) {
  • 伪代码:
for (int i = 0; i < ; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {}if() {}
}

3.3、代码实现

3.3.1、理解冒泡排序

  • 上面的例子不好,我们把数组改成:int arr[] = { 3, 9, -1, 10, -2 }; 这样更能说明冒泡排序的特点
public static void main(String[] args) {int arr[] = { 3, 9, -1, 10, -2 };int temp;System.out.println("排序前");System.out.println(Arrays.toString(arr));for (int j = 0; j < arr.length - 1; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}System.out.println("第一趟排序后的数组");System.out.println(Arrays.toString(arr));for (int j = 0; j < arr.length - 1 - 1; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}System.out.println("第二趟排序后的数组");System.out.println(Arrays.toString(arr));for (int j = 0; j < arr.length - 1 - 2; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}System.out.println("第三趟排序后的数组");System.out.println(Arrays.toString(arr));for (int j = 0; j < arr.length - 1 - 3; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}System.out.println("第四趟排序后的数组");System.out.println(Arrays.toString(arr));
}
  • 程序运行结果
排序前
[3, 9, -1, 10, -2]
第一趟排序后的数组
[3, -1, 9, -2, 10]
第二趟排序后的数组
[-1, 3, -2, 9, 10]
第三趟排序后的数组
[-1, -2, 3, 9, 10]
第四趟排序后的数组
[-2, -1, 3, 9, 10]

3.3.2、编写冒泡排序

  • 测试极端情况
public static void main(String[] args) {int arr[] = { 1, 2, 3, 4, 5, 6 };System.out.println("排序前");System.out.println(Arrays.toString(arr));bubbleSort(arr);
}public static void bubbleSort(int[] arr) {int temp = 0;boolean flag = false;for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {flag = true;temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}System.out.println("第" + (i + 1) + "趟排序后的数组");System.out.println(Arrays.toString(arr));if (!flag) {break;} else {flag = false;}}
}
  • 程序运行结果
排序前
[1, 2, 3, 4, 5, 6]
第1趟排序后的数组
[1, 2, 3, 4, 5, 6]

3.3.3、测试冒泡排序性能

  • 测试代码
public static void main(String[] args) {int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(date1);System.out.println("排序前的时间是=" + date1Str);bubbleSort(arr);Date date2 = new Date();String date2Str = simpleDateFormat.format(date2);System.out.println("排序后的时间是=" + date2Str);}public static void bubbleSort(int[] arr) {int temp = 0;boolean flag = false;for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {flag = true;temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}if (!flag) {break;} else {flag = false;}}
}
  • 程序运行结果
排序前的时间是=2020-07-15 11:44:08
排序后的时间是=2020-07-15 11:44:16

4、选择排序

4.1、选择排序基本介绍

  • 选择式排序也属于内部排序法, 是从欲排序的数据中, 按指定的规则选出某一元素, 再依规定交换位置后达到排序的目的。

4.2、选择排序思想

  • 选择排序(select sorting) 也是一种简单的排序方法。 它的基本思想是(n 是数组大小):

    • 第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0] 交换
    • 第二次从 arr[1]~arr[n-1]中选取最小值, 与 arr[1] 交换
    • 第三次从 arr[2]~arr[n-1]中选取最小值, 与 arr[2] 交换, …,
    • 第 i 次从 arr[i-1]~arr[n-1]中选取最小值, 与 arr[i-1] 交换, …,
    • 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2] 交换,
    • 总共通过 n-1 次, 得到一个按排序码从小到大排列的有序序列。

4.3、选择排序图解

  • 选择排序流程:

    • 第一次循环,默认 arr[0] 是最小的元素,将其与 arr[1]~arr[n-1] 进行比较,找到最小的元素,并与 arr[0] 的位置位置
    • 第二次循环,默认 arr[1] 是最小的元素,将其与 arr[2]~arr[n-1] 进行比较,找到最小的元素,并与 arr[1] 的位置位置
    • 第 i 次循环,默认 arr[i] 是最小的元素,将其与 arr[i+1]~arr[n-1] 进行比较,找到最小的元素,并与 arr[i] 的位置位置
    • 直到循环执行 n - 1 次
  • 总结:两层 for 循环
    • 第一层 for 循环控制走多少趟:for (int i = 0; i < arr.length - 1; i++) {

      • 从数组第一个元素开始,因为每次都是拿当前元素 arr[j] 和其后一个元素 arr[j+1] 进行比较
      • 到数组倒数第二个元素结束,将 arr[arr.length - 2] 与 arr[arr.length - 1] 进行比较后,数组就已经是有序数组
      • 如果数组大小为 n ,那么执行完第 n - 1 趟时,数组就已经是有序数组
    • 第二层 for 循环控制从第几个元素开始执行选择排序:for (int j = i + 1; j < arr.length; j++)
      • 每次进入第二层 for 循环时,先假设当前元素 arr[i] 是最小的元素: min = arr[i]; ,并记录最小元素的下标: index = i;
      • 然后依次和其后面的元素 arr[j] 比较,如果找到比 arr[i] 小的元素,则更新最小值和最小值的索引: min = arr[j]; index = j ;

4.4、代码实现

4.4.1、理解选择排序

  • 一步一步理解选择排序算法
//选择排序
public class SelectSort {public static void main(String[] args) {int[] arr = { 101, 34, 119, 1 };selectSort(arr);}// 选择排序public static void selectSort(int[] arr) {// 使用逐步推导的方式来,讲解选择排序// 第1轮// 原始的数组 : 101, 34, 119, 1// 第一轮排序 : 1, 34, 119, 101// 算法 先简单--》 做复杂, 就是可以把一个复杂的算法,拆分成简单的问题-》逐步解决// 第1轮int minIndex = 0;int min = arr[0];for (int j = 0 + 1; j < arr.length; j++) {if (min > arr[j]) { // 说明假定的最小值,并不是最小min = arr[j]; // 重置minminIndex = j; // 重置minIndex}}// 将最小值,放在arr[0], 即交换if (minIndex != 0) {arr[minIndex] = arr[0];arr[0] = min;}System.out.println("第1轮后~~");System.out.println(Arrays.toString(arr));// 1, 34, 119, 101// 第2轮minIndex = 1;min = arr[1];for (int j = 1 + 1; j < arr.length; j++) {if (min > arr[j]) { // 说明假定的最小值,并不是最小min = arr[j]; // 重置minminIndex = j; // 重置minIndex}}// 将最小值,放在arr[0], 即交换if (minIndex != 1) {arr[minIndex] = arr[1];arr[1] = min;}System.out.println("第2轮后~~");System.out.println(Arrays.toString(arr));// 1, 34, 119, 101// 第3轮minIndex = 2;min = arr[2];for (int j = 2 + 1; j < arr.length; j++) {if (min > arr[j]) { // 说明假定的最小值,并不是最小min = arr[j]; // 重置minminIndex = j; // 重置minIndex}}// 将最小值,放在arr[0], 即交换if (minIndex != 2) {arr[minIndex] = arr[2];arr[2] = min;}System.out.println("第3轮后~~");System.out.println(Arrays.toString(arr));// 1, 34, 101, 119}}
  • 程序运行结果
第1轮后~~
[1, 34, 119, 101]
第2轮后~~
[1, 34, 119, 101]
第3轮后~~
[1, 34, 101, 119]

4.4.2、编写选择排序

  • 编写选择排序算法
//选择排序
public class SelectSort {public static void main(String[] args) {int[] arr = { 101, 34, 119, 1 };selectSort(arr);}// 选择排序public static void selectSort(int[] arr) {// 在推导的过程,我们发现了规律,因此,可以使用for来解决// 选择排序时间复杂度是 O(n^2)for (int i = 0; i < arr.length - 1; i++) {int minIndex = i;int min = arr[i];for (int j = i + 1; j < arr.length; j++) {if (min > arr[j]) { // 说明假定的最小值,并不是最小min = arr[j]; // 重置minminIndex = j; // 重置minIndex}}// 将最小值,放在arr[0], 即交换if (minIndex != i) {arr[minIndex] = arr[i];arr[i] = min;}System.out.println("第" + (i + 1) + "轮后~~");System.out.println(Arrays.toString(arr));}}
}
  • 程序运行结果
第1轮后~~
[1, 34, 119, 101]
第2轮后~~
[1, 34, 119, 101]
第3轮后~~
[1, 34, 101, 119]

4.4.3、测试选择排序性能

  • 测试代码

public class SelectSort {public static void main(String[] args) {int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000);}Date data1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(data1);System.out.println("排序前的时间是=" + date1Str);selectSort(arr);Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}public static void selectSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {int minIndex = i;int min = arr[i];for (int j = i + 1; j < arr.length; j++) {if (min > arr[j]) {min = arr[j];minIndex = j;}}if (minIndex != i) {arr[minIndex] = arr[i];arr[i] = min;}}}
}
  • 程序运行结果
排序前的时间是=2020-07-15 19:59:19
排序前的时间是=2020-07-15 19:59:20

4.5、总结

  • 由于选择排序算法在最内层的 for 循环中,满足 if (min > arr[j]) { 条件后,只需要记录最小值和最小值在数组中的索引,无需像冒泡排序那样每次都要执行交换操作,所以选择排序算法的执行速度比冒泡排序算法快一些

5、插入排序

5.1、插入排序基本介绍

  • 插入式排序属于内部排序法, 是对于欲排序的元素以插入的方式找寻该元素的适当位置, 以达到排序的目的。

5.2、插入排序思想

  • 插入排序(Insertion Sorting) 的基本思想是: 把 n 个待排序的元素看成为一个有序表和一个无序表
  • 开始时有序表中只包含一个元素, 无序表中包含有 n-1 个元素, *排序过程中每次从无序表中取出第一个元素, 把它的排序码依次与有序表元素的排序码进行比较, 将它插入到有序表中的适当位置, 使之成为新的有序表

5.3、插入排序图解

  • 插入排序逻辑:

    • 首先, 将数组分为两个数组,前部分有序数组,后部分是无序数组,我们的目的就是一点一点取出无序数组中的值,将其放到有序数组中区
    • 第一趟:arr[0] 作为有序数组的元素,arr[1] 作为无序数组中第一个元素,将 arr[1] 与 arr[0] 比较,目标是将 arr[1] 插入到有序数组中
    • 第一趟:arr[0] 和 arr[1] 作为有序数组的元素,arr[2] 作为无序数组中第一个元素,将 arr[2] 与 arr[0] 和 arr[1] 比较,目标是将 arr[2] 插入到有序数组中
    • 第 i 趟:arr[0]~arr[i] 作为有序数组的元素,arr[i+1] 作为无序数组中第一个元素,将 arr[i+1] 与 arr[0]~arr[i] 比较,目标是将 arr[i+1] 插入到有序数组中
    • 第 n-1 趟:此时有序数组为 arr[0]~arr[n-2] ,无序数组为 arr[n-1] ,将无序数组中最后一个元素插入到有序数组中即可
    • 如何进行插入?
      • 假设有个指针(index),指向无序数组中的第一个元素,即 arr[index] 是无序数组中的第一个元素,我们定义一个变量来存储该值:int insertVal = arr[index];,现在要将其插入到前面的有序数组中
      • 将 index 前移一步,则指向有序数组最后一个元素,我们定义一个新的变量来存储该指针: insertIndex = index - 1; ,即 arr[insertIndex] 是有序数组最后一个元素
      • 我们需要找到一个比 insertVal 小的值,并将 insertVal 插入在该值后面:
        • 如果 insertVal > arr[insertIndex] ,执行插入
        • 如果 insertVal < arr[insertIndex] ,将有序数组后移,腾出插入空间,insertIndex 指针前移,再看看前一个元素满不满足条件,直到找到插入位置
        • 即循环终止条件为找到插入位置,又分为两种情况:
          • 在有序数组中间找到插入位置
          • insertVal 比有序数组中所有的数都小,插入在数组第一个位置(insertIndex = 0 的情况)
  • 总结:两层循环
    • for 循环控制走多少趟: for(int i = 1; i < arr.length; i++) { ,从数组第一个元素开始到数组最后一个元素结束
    • while 循环不断将指针前移,在有序数组中寻找插入位置,并执行插入: *while (insertIndex >= 0 && insertVal < arr[insertIndex]) {

5.4、代码实现

5.4.1、理解插入排序

  • 理解插入排序算法
public class InsertSort {public static void main(String[] args) {int[] arr = { 101, 34, 119, 1 };insertSort(arr);}// 插入排序public static void insertSort(int[] arr) {// 使用逐步推导的方式来讲解,便利理解// 第1轮 {101, 34, 119, 1}; => {34, 101, 119, 1}// {101, 34, 119, 1}; => {101,101,119,1}// 定义待插入的数int insertVal = arr[1];int insertIndex = 1 - 1; // 即arr[1]的前面这个数的下标// 给insertVal 找到插入的位置// 说明// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置// 3. 就需要将 arr[insertIndex] 后移while (insertIndex >= 0 && insertVal < arr[insertIndex]) {arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]insertIndex--;}// 当退出while循环时,说明插入的位置找到, insertIndex + 1// 举例:理解不了,我们一会 debugarr[insertIndex + 1] = insertVal;System.out.println("第1轮插入");System.out.println(Arrays.toString(arr));// 第2轮insertVal = arr[2];insertIndex = 2 - 1;while (insertIndex >= 0 && insertVal < arr[insertIndex]) {arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]insertIndex--;}arr[insertIndex + 1] = insertVal;System.out.println("第2轮插入");System.out.println(Arrays.toString(arr));// 第3轮insertVal = arr[3];insertIndex = 3 - 1;while (insertIndex >= 0 && insertVal < arr[insertIndex]) {arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]insertIndex--;}arr[insertIndex + 1] = insertVal;System.out.println("第3轮插入");System.out.println(Arrays.toString(arr));}}
  • 程序运行结果
第1轮插入
[34, 101, 119, 1]
第2轮插入
[34, 101, 119, 1]
第3轮插入
[1, 34, 101, 119]

5.4.2、编写插入排序

  • 编写插入排序算法
public class InsertSort {public static void main(String[] args) {int[] arr = { 101, 34, 119, 1 };insertSort(arr);}// 插入排序public static void insertSort(int[] arr) {int insertVal = 0;int insertIndex = 0;//使用for循环来把代码简化for(int i = 1; i < arr.length; i++) {//定义待插入的数insertVal = arr[i];insertIndex = i - 1; // 即arr[1]的前面这个数的下标// 给insertVal 找到插入的位置// 说明// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置// 3. 就需要将 arr[insertIndex] 后移while (insertIndex >= 0 && insertVal < arr[insertIndex]) {arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]insertIndex--;}// 当退出while循环时,说明插入的位置找到, insertIndex + 1// 因为我们找到的元素,即下标为 insertIndex 的元素值比 insertVal 小// 所以我们要将 insertVal 插入到 insertIndex + 1 的位置arr[insertIndex + 1] = insertVal;System.out.println("第"+i+"轮插入");System.out.println(Arrays.toString(arr));}}}
  • 程序运行结果
第1轮插入
[34, 101, 119, 1]
第2轮插入
[34, 101, 119, 1]
第3轮插入
[1, 34, 101, 119]

5.4.3、测试插入排序性能

  • 测试插入排序性能
public class InsertSort {public static void main(String[] args) {// 创建要给80000个的随机的数组int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("插入排序前");Date data1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(data1);System.out.println("排序前的时间是=" + date1Str);insertSort(arr); // 调用插入排序算法Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}// 插入排序public static void insertSort(int[] arr) {int insertVal = 0;int insertIndex = 0;//使用for循环来把代码简化for(int i = 1; i < arr.length; i++) {//定义待插入的数insertVal = arr[i];insertIndex = i - 1; // 即arr[1]的前面这个数的下标// 给insertVal 找到插入的位置// 说明// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置// 3. 就需要将 arr[insertIndex] 后移while (insertIndex >= 0 && insertVal < arr[insertIndex]) {arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]insertIndex--;}// 当退出while循环时,说明插入的位置找到, insertIndex + 1// 举例:理解不了,我们一会 debug//这里我们判断是否需要赋值arr[insertIndex + 1] = insertVal;}}}
  • 程序运行结果
插入排序前
排序前的时间是=2020-07-15 21:49:48
排序前的时间是=2020-07-15 21:49:50

5.5、总结

  • 插入排序在寻找插入位置时,需要对数组元素进行整体挪位,所以效率比选择排序稍低

6、希尔排序

6.1、简单插入排序问题

  • 我们看简单的插入排序可能存在的问题,数组 arr = { 2, 3, 4, 5, 6, 1 } 这时需要插入的数 1(最小),简单插入排序的过程如下
  • 结论: 当需要插入的数是较小的数时, 后移的次数明显增多, 对效率有影响
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}

6.2、希尔排序基本介绍

  • 希尔排序是希尔(Donald Shell) 于 1959 年提出的一种排序算法。 *希尔排序也是一种插入排序, 它是简单插入排序经过改进之后的一个更高效的版本, 也称为缩小增量排序。

6.3、希尔排序基本思想

  • *希尔排序按照增量将数组进行分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止

6.4、希尔排序图解(交换法)

  • 第一次: gap = arr.length/5 = 5 , 将数组分为五组,每个数组元素的索引相差 5

    • 如何完成第一次的排序?

      • 仔细想想,我们需要用一次循环将每组中的元素排序
      • 总共有五组,我们又需要一次循环
      • 所以完成每次排序,需要两层循环
    • 程序代码如下,把 i ,j 都看作是辅助指针:
      • i 与 j 配合使用,可以将指针从数组第一个元素,移动至最后一个元素,目的:把数组遍历一遍
      • j 与 i 配合使用,每次都从数组索引 i 处往前遍历,每次向前移动 gap 个位置,然后进行交换(冒泡排序的意思):看看前面的元素有没有比我的值大,如果前面的元素比我的值大,我就要和他交换位置,跑到前面去
// 希尔排序的第1轮排序
// 因为第1轮排序,是将10个数据分成了 5组
for (int i = 5; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 5; j >= 0; j -= 5) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 5]) {temp = arr[j];arr[j] = arr[j + 5];arr[j + 5] = temp;}}
}
  • 第二次: gap = gap /2 = 2; , 将数组分为两组,每个数组元素的索引相差 2

    • 第一组:

      • i = 2 时,数组从索引 2 处往前遍历,间隔为 2 :将 arr[0]、arr[2] 排序
      • i = 4 时,数组从索引 4 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4] 排序
      • i = 6 时,数组从索引 6 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4]、arr[6] 排序
      • i = 8 时,数组从索引 8 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4]、arr[6]、arr[8] 排序
    • 第二组:
      • i = 3 时,数组从索引 3 处往前遍历,间隔为 2 :将 arr[1]、arr[3] 排序
      • i = 5 时,数组从索引 5 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5] 排序
      • i = 7 时,数组从索引 7 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5]、arr[7] 排序
      • i = 9 时,数组从索引 9 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5]、arr[7]、arr[9] 排序
// 希尔排序的第2轮排序
// 因为第2轮排序,是将10个数据分成了 5/2 = 2组
for (int i = 2; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 2; j >= 0; j -= 2) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 2]) {temp = arr[j];arr[j] = arr[j + 2];arr[j + 2] = temp;}}
}
System.out.println("希尔排序2轮后=" + Arrays.toString(arr));
  • 第三次: gap = gap /2 = 1; , 将数组分为一组,每个数组元素的索引相差 1 ,对于交换法而言,这就是异常冒泡排序

    • i = 1 时,数组从索引 1 处往前遍历,间隔为 1 :将 arr[0]、arr[1] 排序
    • i = 2 时,数组从索引 2 处往前遍历,间隔为 1 :将 arr[0]、arr[1]、arr[2] 排序
    • i = 3 时,数组从索引 3 处往前遍历,间隔为 1 :将 arr[0]、arr[1]、arr[2]、arr[3] 排序
// 希尔排序的第3轮排序
// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
for (int i = 1; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 1; j >= 0; j -= 1) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}
}
System.out.println("希尔排序3轮后=" + Arrays.toString(arr));
  • 总结:每次使用循环改变 gap 的值(初始值:数组大小/2 ,之后:gap = gap/2),然后在改变 gap 的循环中嵌套上面的双层 for 循环

    • 改变 gap : for (int gap = arr.length / 2; gap > 0; gap /= 2) {
    • 内层循环:实现对每组数组的排序
for (int i = gap; i < arr.length; i++) {// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gapfor (int j = i - gap; j >= 0; j -= gap) {
  • 希尔排序伪代码
for (int gap = arr.length / 2; gap > 0; gap /= 2) {for (int i = gap; i < arr.length; i++) {// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gapfor (int j = i - gap; j >= 0; j -= gap) {// 对每组进行冒泡排序}}
}

6.5、代码实现

6.5.1、理解希尔排序(交换法)

  • 理解基于交换法的希尔排序
public class ShellSort {public static void main(String[] args) {int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };shellSort(arr);}// 使用逐步推导的方式来编写希尔排序// 希尔排序时, 对有序序列在插入时采用交换法,// 思路(算法) ===> 代码public static void shellSort(int[] arr) {int temp = 0;// 希尔排序的第1轮排序// 因为第1轮排序,是将10个数据分成了 5组for (int i = 5; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 5; j >= 0; j -= 5) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 5]) {temp = arr[j];arr[j] = arr[j + 5];arr[j + 5] = temp;}}}System.out.println("希尔排序1轮后=" + Arrays.toString(arr));// 希尔排序的第2轮排序// 因为第2轮排序,是将10个数据分成了 5/2 = 2组for (int i = 2; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 2; j >= 0; j -= 2) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 2]) {temp = arr[j];arr[j] = arr[j + 2];arr[j + 2] = temp;}}}System.out.println("希尔排序2轮后=" + Arrays.toString(arr));// 希尔排序的第3轮排序// 因为第3轮排序,是将10个数据分成了 2/2 = 1组for (int i = 1; i < arr.length; i++) {// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5for (int j = i - 1; j >= 0; j -= 1) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + 1]) {temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}System.out.println("希尔排序3轮后=" + Arrays.toString(arr));}
}
  • 程序运行结果
希尔排序1轮后=[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
希尔排序2轮后=[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
希尔排序3轮后=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

6.5.2、编写希尔排序(交换法)

  • 编写基于交换法的希尔排序算法
public class ShellSort {public static void main(String[] args) {int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };shellSort(arr);}// 使用逐步推导的方式来编写希尔排序// 希尔排序时, 对有序序列在插入时采用交换法,// 思路(算法) ===> 代码public static void shellSort(int[] arr) {int temp = 0;int count = 0;// 根据前面的逐步分析,使用循环处理for (int gap = arr.length / 2; gap > 0; gap /= 2) {for (int i = gap; i < arr.length; i++) {// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gapfor (int j = i - gap; j >= 0; j -= gap) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + gap]) {temp = arr[j];arr[j] = arr[j + gap];arr[j + gap] = temp;}}}System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));}}
  • 程序运行结果
希尔排序第1轮 =[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
希尔排序第2轮 =[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
希尔排序第3轮 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

6.5.3、测试希尔排序(交换法)性能

  • 测试基于交换法的希尔排序算法性能
public class ShellSort {public static void main(String[] args) {// 创建要给80000个的随机的数组int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("排序前");Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(date1);System.out.println("排序前的时间是=" + date1Str);shellSort(arr); // 交换式Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}// 使用逐步推导的方式来编写希尔排序// 希尔排序时, 对有序序列在插入时采用交换法,// 思路(算法) ===> 代码public static void shellSort(int[] arr) {int temp = 0;int count = 0;// 根据前面的逐步分析,使用循环处理for (int gap = arr.length / 2; gap > 0; gap /= 2) {for (int i = gap; i < arr.length; i++) {// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gapfor (int j = i - gap; j >= 0; j -= gap) {// 如果当前元素大于加上步长后的那个元素,说明交换if (arr[j] > arr[j + gap]) {temp = arr[j];arr[j] = arr[j + gap];arr[j + gap] = temp;}}}}}}
  • 程序运行结果
排序前
排序前的时间是=2020-07-16 10:22:27
排序前的时间是=2020-07-16 10:22:33
  • 分析:由于使用交换法实现希尔排序算法,所以基于交换法的希尔排序算法比简单选择排序算法更慢, *所以我们一定要编写基于插入法的希尔排序算法

6.5.4、编写希尔排序(插入法)

  • 编写基于插入法的希尔排序算法:

    • 记录当前位置的元素值 int temp = arr[j]; ,从当前元素前一个位置开始,往前寻找,每次移动 gap 个距离

      • 如果 temp < arr[j - gap] :

        • 将数组元素后移,腾出插入空间: arr[j] = arr[j - gap];
        • 然后继续往前找: j -= gap;
      • 如果 temp > arr[j - gap] ,找到插入位置,执行插入 arr[j] = temp; ,因为在上一步已经腾出了插入空间,并且将指针 j 前移,所以可直接插入
      • 如果 找到数组最前面还是没有找到插入位置: j - gap < 0 ,则证明 temp 需要插入在数组最前面
    • 仅仅就是将之前交换法的冒泡操作替换成了插入操作
public class ShellSort {public static void main(String[] args) {int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };System.out.println("排序前");System.out.println(Arrays.toString(arr));shellSort(arr);System.out.println("排序前");System.out.println(Arrays.toString(arr));}// 对交换式的希尔排序进行优化->移位法public static void shellSort(int[] arr) {// 增量gap, 并逐步的缩小增量for (int gap = arr.length / 2; gap > 0; gap /= 2) {// 从第gap个元素,逐个对其所在的组进行直接插入排序for (int i = gap; i < arr.length; i++) {int j = i;int temp = arr[j];if (arr[j] < arr[j - gap]) {while (j - gap >= 0 && temp < arr[j - gap]) {// 移动arr[j] = arr[j - gap];j -= gap;}// temp 比 arr[j - gap] 大,所以需要插入在 j 的位置arr[j] = temp;}}}}}
  • 程序运行结果
排序前
[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
排序前
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

6.5.5、测试希尔排序(插入法)性能

  • 测试基于插入法的希尔排序算法性能
public class ShellSort {public static void main(String[] args) {// 创建要给80000个的随机的数组int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("排序前");Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(date1);System.out.println("排序前的时间是=" + date1Str);shellSort(arr); // 交换式Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}// 对交换式的希尔排序进行优化->移位法public static void shellSort(int[] arr) {// 增量gap, 并逐步的缩小增量for (int gap = arr.length / 2; gap > 0; gap /= 2) {// 从第gap个元素,逐个对其所在的组进行直接插入排序for (int i = gap; i < arr.length; i++) {int j = i;int temp = arr[j];if (arr[j] < arr[j - gap]) {while (j - gap >= 0 && temp < arr[j - gap]) {// 移动arr[j] = arr[j - gap];j -= gap;}// 当退出while后,就给temp找到插入的位置arr[j] = temp;}}}}}
  • 程序运行结果:1s 都不到,果然快啊
排序前
排序前的时间是=2020-07-16 11:02:20
排序前的时间是=2020-07-16 11:02:20
  • 八百万个数据的测试结果
排序前
排序前的时间是=2020-07-16 14:38:55
排序前的时间是=2020-07-16 14:38:57

7、快速排序

7.1、快排简介

  1. 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
  2. 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
  3. 快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
  4. 快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。
  5. 虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
  6. 快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

7.2、代码思路

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
  3. 在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  4. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

快排流程分析

以 {25, 84, 21, 47, 15, 27, 68, 35, 20} 数列为例(下面的流程和上面的动图其实不太一样,不过大体思想是一样的)

  1. 第一趟:val = 25; 先取出来保存着
  • { 20, 84, 21, 47, 15, 27, 68, 35, 20}
  • {20, 84, 21, 47, 15, 27, 68, 35, 84}
  • {20, 15, 21, 47, 15, 27, 68, 35, 84}
  • {20, 15, 21, 47, 47, 27, 68, 35, 84}
  • {20, 15, 21, 25, 47, 27, 68, 35, 84}
  1. 第二趟:val = 20; 先取出来保存着
  • { 15, 15, 21}
  • {15, 20, 21}
  1. 以此类推 …

7.3、代码实现

7.3.1、编写快排算法

  • 快排代码
private static void quickSort(int[] arr, int left, int right) {if (left < right) {int partitionIndex = partition(arr, left, right);quickSort(arr, left, partitionIndex - 1);quickSort(arr, partitionIndex + 1, right);}
}private static int partition(int[] arr, int left, int right) {int pivot = arr[left];//终止while循环以后left和right一定相等的while (left < right) {while (left < right && arr[right] >= pivot) {--right;}arr[left] = arr[right];while (left < right && arr[left] <= pivot) {++left;}arr[right] = arr[left];}arr[left] = pivot;//right可以改为leftreturn left;
}
  • 测试代码
public static void main(String[] args) {int[] arr = {25, 84, 21, 47, 15, 27, 68, 35, 20};quickSort(arr, 0, arr.length - 1);System.out.println(Arrays.toString(arr));
}
  • 程序输出
arr=[15, 20, 21, 25, 27, 35, 47, 68, 84]

7.3.2、测试快速排序性能

  • 编测试快速排序算法性能
public class QuickSort {public static void main(String[] args) {// 创建要给80000个的随机的数组int[] arr = new int[80000];for (int i = 0; i < 80000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("排序前");Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(date1);System.out.println("排序前的时间是=" + date1Str);quickSort(arr, 0, arr.length - 1); // 交换式Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}private static void quickSort(int[] arr, int left, int right) {if (left < right) {int partitionIndex = partition(arr, left, right);quickSort(arr, left, partitionIndex - 1);quickSort(arr, partitionIndex + 1, right);}}private static int partition(int[] arr, int left, int right) {int pivot = arr[left];// 终止while循环以后left和right一定相等的while (left < right) {while (left < right && arr[right] >= pivot) {--right;}arr[left] = arr[right];while (left < right && arr[left] <= pivot) {++left;}arr[right] = arr[left];}arr[left] = pivot;// right可以改为leftreturn left;}
}
  • 程序运行结果:卧槽,八百个数据只需要 1s ,甚至可能还不到。。。
排序前
排序前的时间是=2020-08-06 18:43:44
排序前的时间是=2020-08-06 18:43:44

8、归并排序

8.1、归并排序基本介绍

  • 归并排序(MERGE-SORT) 是利用归并的思想实现的排序方法, 该算法采用经典的分治(divide-and-conquer)策略
  • *分治法将问题分(divide)成一些小的问题然后递归求解, 而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起, 即分而治之

8.2、归并排序思想

  • 分 --> 治

8.3、归并排序代码思路

  • 合并时,其实是拿着原数组(arr)中两个相邻的子数组(arr1、arr2)进行合并,我们使用三个指针,来表示两个子数组在原数组中的位置

    • arr[left] ~ arr[mid] 为 arr1
    • arr[mid + 1] ~ arr[right] 为 arr2
  • 如何合并?
    • 首先,需要一个临时的 temp 数组,其大小与原数组 arr 一样
    • 定义辅助指针 i 遍历 arr1 ,定义辅助指针 j 遍历 arr2 ,原则就是,把 arr1 和 arr2 中的数往 temp 中放,使得 temp[left] ~ temp[right] 是有序数组
    • 最后把 temp 临时数组中的数据拷贝回原数组中(个人认为,最后一下次再拷贝回去就行。。。)
  • 如何分?
    • 向左递归拆分:mergeSort(arr, left, mid, temp);
    • 向右递归拆分:mergeSort(arr, mid + 1, right, temp);

8.4、代码实现

8.4.1、编写归并排序算法

  • 归并排序算法实现代码
public class MergetSort {public static void main(String[] args) {int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 };int temp[] = new int[arr.length]; // 归并排序需要一个额外空间mergeSort(arr, 0, arr.length - 1, temp);System.out.println("归并排序后=" + Arrays.toString(arr));}// 分+合方法public static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = (left + right) / 2; // 中间索引// 向左递归进行分解mergeSort(arr, left, mid, temp);// 向右递归进行分解mergeSort(arr, mid + 1, right, temp);// 合并merge(arr, left, mid, right, temp);}}// 合并的方法/*** * @param arr   排序的原始数组* @param left  左边有序序列的初始索引* @param mid   中间索引* @param right 右边索引* @param temp  做中转的数组*/public static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 初始化i, 左边有序序列的初始索引int j = mid + 1; // 初始化j, 右边有序序列的初始索引int t = 0; // 指向temp数组的当前索引// (一)// 先把左右两边(有序)的数据按照规则填充到temp数组// 直到左右两边的有序序列,有一边处理完毕为止while (i <= mid && j <= right) {// 继续// 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素// 即将左边的当前元素,填充到 temp数组// 然后 t++, i++if (arr[i] <= arr[j]) {temp[t] = arr[i];t += 1;i += 1;} else { // 反之,将右边有序序列的当前元素,填充到temp数组temp[t] = arr[j];t += 1;j += 1;}}// (二)// 把有剩余数据的一边的数据依次全部填充到tempwhile (i <= mid) { // 左边的有序序列还有剩余的元素,就全部填充到temptemp[t] = arr[i];t += 1;i += 1;}while (j <= right) { // 右边的有序序列还有剩余的元素,就全部填充到temptemp[t] = arr[j];t += 1;j += 1;}// (三)// 将temp数组的元素拷贝到arr// 注意,并不是每次都拷贝所有t = 0;int tempLeft = left; //// 第一次合并 tempLeft = 0 , right = 1 //第二次: tempLeft = 2 right = 3 //第三次: tL=0 ri=3// 最后一次 tempLeft = 0 right = 7while (tempLeft <= right) {arr[tempLeft] = temp[t];t += 1;tempLeft += 1;}}}
  • 程序运行结果
归并排序后=[1, 2, 3, 4, 5, 6, 7, 8]

8.4.2、测试归并排序性能

  • 测试归并排序算法的性能
public class MergetSort {public static void main(String[] args) {// 测试快排的执行速度// 创建要给80000个的随机的数组int[] arr = new int[8000000];for (int i = 0; i < 8000000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("排序前");Date data1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(data1);System.out.println("排序前的时间是=" + date1Str);int temp[] = new int[arr.length]; // 归并排序需要一个额外空间mergeSort(arr, 0, arr.length - 1, temp);Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);// System.out.println("归并排序后=" + Arrays.toString(arr));}// 分+合方法public static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = (left + right) / 2; // 中间索引// 向左递归进行分解mergeSort(arr, left, mid, temp);// 向右递归进行分解mergeSort(arr, mid + 1, right, temp);// 合并merge(arr, left, mid, right, temp);}}// 合并的方法/*** * @param arr   排序的原始数组* @param left  左边有序序列的初始索引* @param mid   中间索引* @param right 右边索引* @param temp  做中转的数组*/public static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 初始化i, 左边有序序列的初始索引int j = mid + 1; // 初始化j, 右边有序序列的初始索引int t = 0; // 指向temp数组的当前索引// (一)// 先把左右两边(有序)的数据按照规则填充到temp数组// 直到左右两边的有序序列,有一边处理完毕为止while (i <= mid && j <= right) {// 继续// 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素// 即将左边的当前元素,填充到 temp数组// 然后 t++, i++if (arr[i] <= arr[j]) {temp[t] = arr[i];t += 1;i += 1;} else { // 反之,将右边有序序列的当前元素,填充到temp数组temp[t] = arr[j];t += 1;j += 1;}}// (二)// 把有剩余数据的一边的数据依次全部填充到tempwhile (i <= mid) { // 左边的有序序列还有剩余的元素,就全部填充到temptemp[t] = arr[i];t += 1;i += 1;}while (j <= right) { // 右边的有序序列还有剩余的元素,就全部填充到temptemp[t] = arr[j];t += 1;j += 1;}// (三)// 将temp数组的元素拷贝到arr// 注意,并不是每次都拷贝所有t = 0;int tempLeft = left; //// 第一次合并 tempLeft = 0 , right = 1 //第二次: tempLeft = 2 right = 3 //第三次: tL=0 ri=3// 最后一次 tempLeft = 0 right = 7while (tempLeft <= right) {arr[tempLeft] = temp[t];t += 1;tempLeft += 1;}}}
  • 程序运行结果:八百万数据用了 1s ,也挺快
排序前
排序前的时间是=2020-07-16 16:18:32
排序前的时间是=2020-07-16 16:18:33

8.5、总结

  • 先将数组分为左右两半,先执行左半边递归:

    • 首先执行左递归到最深层,条件 if (left < right) 不满足,开始执行合并,合并 { 8, 4 } 到临时数组 temp 中,变为有序数组 { 4, 8 } ,再拷贝回原数组 arr 中
    • 然后执行最深层的右递归,条件 if (left < right) 不满足,开始执行合并,合并 { 5, 7 } 到临时数组 temp 中,变为有序数组 { 2, 7 } ,再拷贝回原数组 arr 中
    • 合并完后,递归回溯至上一节,开始执行合并,合并 { 4, 5, 7, 8 } 到临时数组 temp 中,变为有序数组 { 4, 5, 7, 8 } ,再拷贝回原数组 arr 中
  • 右左半边的递归也是同样的道理

9、基数排序

9.1、基数排序基本介绍

  • 基数排序(radix sort) 属于"分配式排序" (distribution sort) , 又称"桶子法" (bucket sort) 或 bin sort, 顾名思义, 它是通过键值的各个位的值, 将要排序的元素分配至某些"桶" 中, 达到排序的作用
  • 基数排序法是属于稳定性的排序, 基数排序法的是效率高的稳定性排序法
  • 基数排序(Radix Sort)是桶排序的扩展
  • 基数排序是 1887 年赫尔曼· 何乐礼发明的。 它是这样实现的: 将整数按位数切割成不同的数字, 然后按每个位数分别比较。

9.2、基数排序思想

  • 将所有待比较数值统一为同样的数位长度, 数位较短的数前面补零。
  • 然后, *从最低位开始, 依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

9.3、基数排序图解

  • 有 10 个桶,对应编号为 0~9

  • 步骤

    • 第一步:根据原数组 arr 中每个元素的个位数,将其依次放入 0~9 号桶中(每个桶从前往后放),放置完毕后,再将桶中的数据依次取出(每个桶从前往后取),放回原数组 arr 中,这样原数组 arr 中个位数的元素就已经按照顺序排好了
    • 第二步:根据原数组 arr 中每个元素的十位数,将其依次放入 0~9 号桶中(每个桶从前往后放),放置完毕后,再将桶中的数据依次取出(每个桶从前往后取),放回原数组 arr 中,这样原数组 arr 中十位数 + 个位数的元素就已经按照顺序排好了
    • 第三步:根据原数组 arr 中每个元素的百位数,将其依次放入 0~9 号桶中(每个桶从前往后放),放置完毕后,再将桶中的数据依次取出(每个桶从前往后取),放回原数组 arr 中,这样原数组 arr 中百位数 + 十位数 + 个位数的元素就已经按照顺序排好了
  • 何时排序完毕?当数组中最长位数的元素处理完毕,排序完成

  • 桶的容量如何确定?假设数组每个元素位数相同,那么单个桶最大容量即为数组容量,我们用一个二维数组来表示桶: int[][] bucket = new int[10][arr.length];

  • 我们如何知道每桶中装了几个元素?这也需要记录,用一个一维数组来记录: int[] bucketElementCounts = new int[10];

  • 总结:

    • 假设数组中元素的最长位数为 maxLength ,则处理完 maxLength 位数后,数组排序完毕:* for(int i = 0 , n = 1; i < maxLength; i++, n = 10) {
    • 使用一个 for 循环处理原一维数组 arr ,将其放入桶中 for(int j = 0; j < arr.length; j++) {
    • 使用两层 for 循环,处理 10 个 桶,将其中的元素放回原一维数组中 for (int k = 0; k < bucketElementCounts.length; k++) {
      if (bucketElementCounts[k] != 0) {
      *for (int l = 0; l < bucketElementCounts[k]; l++) {

9.4、代码实现

9.4.1、理解基数排序

  • 逐步分解,理解基数排序算法
public class RadixSort {public static void main(String[] args) {int arr[] = { 53, 3, 542, 748, 14, 214};radixSort(arr);System.out.println("基数排序后 " + Arrays.toString(arr));}//基数排序方法public static void radixSort(int[] arr) {//      //根据前面的推导过程,我们可以得到最终的基数排序代码//1. 得到数组中最大的数的位数int max = arr[0]; //假设第一数就是最大数for(int i = 1; i < arr.length; i++) {if (arr[i] > max) {max = arr[i];}}//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组//说明//1. 二维数组包含10个一维数组//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length//3. 名明确,基数排序是使用空间换时间的经典算法int[][] bucket = new int[10][arr.length];//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数//可以这里理解//比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数int[] bucketElementCounts = new int[10];//第1轮(针对每个元素的个位进行排序处理)for(int j = 0; j < arr.length; j++) {//取出每个元素的个位的值int digitOfElement = arr[j] / 1 % 10;//放入到对应的桶中bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)int index = 0;//遍历每一桶,并将桶中是数据,放入到原数组for(int k = 0; k < bucketElementCounts.length; k++) {//如果桶中,有数据,我们才放入到原数组if(bucketElementCounts[k] != 0) {//循环该桶即第k个桶(即第k个一维数组), 放入for(int l = 0; l < bucketElementCounts[k]; l++) {//取出元素放入到arrarr[index++] = bucket[k][l];}}//第l轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!bucketElementCounts[k] = 0;}System.out.println("第1轮,对个位的排序处理 arr =" + Arrays.toString(arr));//第2轮(针对每个元素的十位进行排序处理)for (int j = 0; j < arr.length; j++) {// 取出每个元素的十位的值int digitOfElement = arr[j] / 10  % 10; //748 / 10 => 74 % 10 => 4// 放入到对应的桶中bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)index = 0;// 遍历每一桶,并将桶中是数据,放入到原数组for (int k = 0; k < bucketElementCounts.length; k++) {// 如果桶中,有数据,我们才放入到原数组if (bucketElementCounts[k] != 0) {// 循环该桶即第k个桶(即第k个一维数组), 放入for (int l = 0; l < bucketElementCounts[k]; l++) {// 取出元素放入到arrarr[index++] = bucket[k][l];}}//第2轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!bucketElementCounts[k] = 0;}System.out.println("第2轮,对个位的排序处理 arr =" + Arrays.toString(arr));//第3轮(针对每个元素的百位进行排序处理)for (int j = 0; j < arr.length; j++) {// 取出每个元素的百位的值int digitOfElement = arr[j] / 100 % 10; // 748 / 100 => 7 % 10 = 7// 放入到对应的桶中bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)index = 0;// 遍历每一桶,并将桶中是数据,放入到原数组for (int k = 0; k < bucketElementCounts.length; k++) {// 如果桶中,有数据,我们才放入到原数组if (bucketElementCounts[k] != 0) {// 循环该桶即第k个桶(即第k个一维数组), 放入for (int l = 0; l < bucketElementCounts[k]; l++) {// 取出元素放入到arrarr[index++] = bucket[k][l];}}//第3轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!bucketElementCounts[k] = 0;}System.out.println("第3轮,对个位的排序处理 arr =" + Arrays.toString(arr));}}
  • 程序运行结果
第1轮,对个位的排序处理 arr =[542, 53, 3, 14, 214, 748]
第2轮,对个位的排序处理 arr =[3, 14, 214, 542, 748, 53]
第3轮,对个位的排序处理 arr =[3, 14, 53, 214, 542, 748]
基数排序后 [3, 14, 53, 214, 542, 748]

9.4.2、编写基数排序

  • 编写基数排序算法
public class RadixSort {public static void main(String[] args) {int arr[] = { 53, 3, 542, 748, 14, 214 };radixSort(arr);System.out.println("基数排序后 " + Arrays.toString(arr));}// 基数排序方法public static void radixSort(int[] arr) {//根据前面的推导过程,我们可以得到最终的基数排序代码    //1. 得到数组中最大的数的位数int max = arr[0]; //假设第一数就是最大数for(int i = 1; i < arr.length; i++) {if (arr[i] > max) {max = arr[i];}}//得到最大数是几位数int maxLength = (max + "").length();//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组//说明//1. 二维数组包含10个一维数组//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length//3. 名明确,基数排序是使用空间换时间的经典算法int[][] bucket = new int[10][arr.length];//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数//可以这里理解//比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数int[] bucketElementCounts = new int[10];// n=1 表示处理个位,n=10表示处理十位,n=100表示处理百位 ......for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..for(int j = 0; j < arr.length; j++) {//取出每个元素的对应位的值int digitOfElement = arr[j] / n % 10;//放入到对应的桶中bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)int index = 0;//遍历每一桶,并将桶中的数据,放入到原数组for(int k = 0; k < bucketElementCounts.length; k++) {//如果桶中,有数据,我们才放入到原数组// 遍历第k个桶(即第k个一维数组), 将桶中的数据放回原数组中for (int l = 0; l < bucketElementCounts[k]; l++) {// 取出元素放入到arrarr[index++] = bucket[k][l];}//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!bucketElementCounts[k] = 0;                }System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));}}}
  • 程序运行结果
第1轮,对个位的排序处理 arr =[542, 53, 3, 14, 214, 748]
第2轮,对个位的排序处理 arr =[3, 14, 214, 542, 748, 53]
第3轮,对个位的排序处理 arr =[3, 14, 53, 214, 542, 748]
基数排序后 [3, 14, 53, 214, 542, 748]

9.4.3、测试基数排序性能

  • 测试基数排序算法的性能
public class RadixSort {public static void main(String[] args) {// 80000000 * 11 * 4 / 1024 / 1024 / 1024 =3.3G int[] arr = new int[8000000];for (int i = 0; i < 8000000; i++) {arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数}System.out.println("排序前");Date data1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String date1Str = simpleDateFormat.format(data1);System.out.println("排序前的时间是=" + date1Str);radixSort(arr);Date data2 = new Date();String date2Str = simpleDateFormat.format(data2);System.out.println("排序前的时间是=" + date2Str);}// 基数排序方法public static void radixSort(int[] arr) {//根据前面的推导过程,我们可以得到最终的基数排序代码   //1. 得到数组中最大的数的位数int max = arr[0]; //假设第一数就是最大数for(int i = 1; i < arr.length; i++) {if (arr[i] > max) {max = arr[i];}}//得到最大数是几位数int maxLength = (max + "").length();//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组//说明//1. 二维数组包含10个一维数组//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length//3. 名明确,基数排序是使用空间换时间的经典算法int[][] bucket = new int[10][arr.length];//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数//可以这里理解//比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数int[] bucketElementCounts = new int[10];// n=1 表示处理个位,n=10表示处理十位,n=100表示处理百位 ......for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..for(int j = 0; j < arr.length; j++) {//取出每个元素的对应位的值int digitOfElement = arr[j] / n % 10;//放入到对应的桶中bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)int index = 0;//遍历每一桶,并将桶中的数据,放入到原数组for(int k = 0; k < bucketElementCounts.length; k++) {//如果桶中,有数据,我们才放入到原数组// 遍历第k个桶(即第k个一维数组), 将桶中的数据放回原数组中for (int l = 0; l < bucketElementCounts[k]; l++) {// 取出元素放入到arrarr[index++] = bucket[k][l];}//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!bucketElementCounts[k] = 0;                }System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));}}}
  • 程序运行结果:可以啊,八百万数据 1s 就排好了,但是太占空间了
排序前
排序前的时间是=2020-07-16 18:16:21
排序前的时间是=2020-07-16 18:16:22

9.5、基数排序的说明

  • 基数排序是对传统桶排序的扩展, 速度很快
  • 基数排序是经典的空间换时间的方式, 占用内存很大,当对海量数据排序时, 容易造成 OutOfMemoryError 。
  • 基数排序时稳定的。 [注:假定在待排序的记录序列中, 存在多个具有相同的关键字的记录, 若经过排序, 这些记录的相对次序保持不变, 即在原序列中, r[i]=r[j], 且 r[i]在 r[j]之前, 而在排序后的序列中, r[i]仍在 r[j]之前,则称这种排序算法是稳定的; 否则称为不稳定的]
  • 有负数的数组, 我们不用基数排序来进行排序, 如果要支持负数, 参考: https://code.i-harness.com/zh-CN/q/e98fa9

10、常用排序算法总结和对比

10.1、排序算法的比较图

10.2、相关术语解释

  • 稳定:如果 a 原本在 b 前面, 而 a=b, 排序之后 a 仍然在 b 的前面;
  • 不稳定:如果 a 原本在 b 的前面, 而 a=b, 排序之后 a 可能会出现在 b 的后面;
  • 内排序: 所有排序操作都在内存中完成;
  • 外排序: 由于数据太大, 因此把数据放在磁盘中, 而排序通过磁盘和内存的数据传输才能进行;
  • 时间复杂度: 一个算法执行所耗费的时间。
  • 空间复杂度: 运行完一个程序所需内存的大小。
  • n: 数据规模
  • k: “桶” 的个数
  • In-place:不占用额外内存
  • Out-place:占用额外内存

数据结构之你没见过的排序算法!相关推荐

  1. 数据结构(三) 用java实现七种排序算法。

    很多时候,听别人在讨论快速排序,选择排序,冒泡排序等,都觉得很牛逼,心想,卧槽,排序也分那么多种,就觉得别人很牛逼呀,其实不然,当我们自己去了解学习后发现,并没有想象中那么难,今天就一起总结一下各种排 ...

  2. python实现排序算法_数据结构之(3)python实现排序算法

    常用排序与插入算法 冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直 ...

  3. 【数据结构基础应用】【查找和排序算法】

    代码参考<妙趣横生的算法.C语言实现> 文章目录 前言 1.顺序查找 2.折半查找 3.直接插入排序 4.选择排序 5.冒泡排序 6.希尔排序 7.快速排序 8.堆排序 9.排序算法性能比 ...

  4. 数据结构(严蔚敏)的一些排序算法源代码

    最近找工作时经常会被问到各种排序算法,现在把严蔚敏书中的排序算法摘抄出来,以便随时学习,顺便测试下windows live writer的代码着色插件是否好用 1.InsertSort直接插入排序 隐 ...

  5. 数据结构常考题 —— 八种经典内部排序算法

    经典排序算法 我们经典的排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 算法复杂度如下图: 下面我们 ...

  6. 数据结构之你没见过的稀疏数组和队列刨析!

    大家好!,我是小刘,很长一段时间,没更新了,今天和大家复习一下,数据结构中的稀疏数组,仅作分享,一起交流,哈哈! 1.稀疏数组 1.1.实际需求 编写的五子棋程序中,有存盘退出和续上盘的功能 因为该二 ...

  7. 高效排序算法——希尔排序、堆排序、归并排序、快速排序

    如标题,这里讨论的是基于比较的排序算法中最高效的三种算法和希尔排序.堆排序.归并排序.快速排序的平均时间复杂度均为O(NlogN).前面有介绍过O(N2)的三种简单排序算法(见三大简单排序算法--插入 ...

  8. 排序算法--睡眠排序

    我们学数据结构的时候会学到多种排序算法,基本上都是基于比较的排序,下面的这个排序算法并不是基于比较,确切的说它是基于cpu调度算法实现的,这个算法的作者称之为--睡眠排序. 它的基本思想是,对一组数据 ...

  9. [Alg]排序算法之分布排序

    [Alg]排序算法之分布排序 作者:屎壳郎 日期:Aug 2021 版次:初版 简介: 分布排序是与归并排序截然相反的处理思路,归并排序是逐步融合归并,而分布排序是分组然后合并,再分组再合并,所以分布 ...

最新文章

  1. Kafka基础入门篇
  2. SQL2005的安装与 使用
  3. Java的知识点18——数组存储表格数据、冒泡排序的基础算法、冒泡排序的优化算法、二分法查找
  4. java.lang.InstantiationException
  5. 查看SQL SERVER数据库的连接数
  6. c语言可变入参中的每个参数的类型可以不同,编程入门:浅谈C语言的可变参数
  7. 文件流、目录流、文件描述符总结
  8. 程序员未来会成为非常内卷式的职业吗?
  9. [BZOJ1415]聪聪和可可
  10. OpenCV_(Corner Detect with Morphology) 基于形态学滤波的角点检测
  11. shell脚本中一些日期的定义
  12. 计算机领域有哪些常见的比赛
  13. Android 手机 超级终端命令解析
  14. MATLAB鲁棒控制器实现
  15. 毕业设计:舆情监测系统(SpringBoot+NLP)
  16. 福利:推荐一个免费的抠图网站
  17. Python读取snappy后缀文件
  18. 中南大学官网计算机学院,中南大学
  19. 示波器X1探头和X10探头
  20. JAVA反色计算方法的改进和修正

热门文章

  1. eap wifi 证书_如何手动连接802.1x EAP证书加密WIFI
  2. 【基础算法训练】—— 01背包 + 排序
  3. Umi部署pages多页面访问配置
  4. MATLAB基本使用方法
  5. C语言:歌德巴赫猜想:2000以内的正偶数(不包括2)都能够分解为两个质数之和
  6. 机器人开发--设计范式
  7. 批处理之批量修改文件扩展名
  8. 手把手教你打通车载蓝牙与手机app的音频信息传输车载反向控制手机app
  9. linux 忽略错误信息,linux – 由于文件模式错误而忽略/etc/logrotate.conf
  10. 新加坡国立大学招收博士生/博士后/研究助理/访问学生