服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - 手撕十大经典排序算法

手撕十大经典排序算法

2021-11-01 22:42程序员千羽 编程技术

对于程序员来说,算法好的人,写起代码来特别快(我们班有几位同学正是)。而且在遇到某个问题时,他想的到解决办法很多。

手撕十大经典排序算法

源码点这里GitHub: https://github.com/nateshao/leetcode

手撕十大经典排序算法

死磕算法系列

在面试中,基本上来第一面就是算法,特别是大厂,对算法的要求特别高!

对于程序员来说,算法好的人,写起代码来特别快(我们班有几位同学正是)。而且在遇到某个问题时,他想的到解决办法很多。

当然,学算法是件相对枯燥的事情,不过,当你懂得算法这种思路之后,你会发现,算法真的是一个很神奇的东西,而且,算法引出的思想非常重要!!所以,千羽也会不断的学习死磕算法系列文章,和大家一起学习,一起进步。这篇文章主要讲解十大经典排序算法。话不多说,冲冲冲!

认识时间复杂度

常数时间的操作:一个操作如果和数据量没有关系,每次都是 固定时间内完成的操作,叫做常数操作。时间复杂度为一个算法流程中,常数操作数量的指标。常用O (读作big O)来表示。具体来说,在常数操作数量的表达式中, 只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分 如果记为f(N),那么时间复杂度为O(f(N))。

评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项时间。

十大经典排序算法

算法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最优) 空间复杂度 稳定性
冒泡排序 O(n2) O(n2) O(n) O(1) 稳定
选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
插入排序 O(n2) O(n2) O(n) O(1) 稳定
希尔排序 O(n1.3) O(n2) O(n) O(1) 不稳定
快速排序 O(nlog2n) O(n2) O(nlog2n) O(nlog2n) 不稳定
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定
桶排序 O(n2) O(n*k) O(n) O(n+k) 稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 稳定

排序的定义

对一序列对象根据某个关键字进行排序。

术语说明

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

1. 冒泡排序

时间复杂度 ,额外空间复杂度

冒泡排序(Bubble Sort)又称为泡式排序,是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

思路:

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. /** 
  4.  * @date Created by 邵桐杰 on 2021/10/29 16:52 
  5.  * @微信公众号 程序员千羽 
  6.  * @个人网站 www.nateshao.cn 
  7.  * @博客 https://nateshao.gitee.io 
  8.  * @GitHub https://github.com/nateshao 
  9.  * @Gitee https://gitee.com/nateshao 
  10.  * Description: 原文链接 https://zfhelo.gitee.io/2020/06/14/1/ 
  11.  */ 
  12. public class Code_01_BubbleSortTest { 
  13.     public static void main(String[] args) { 
  14.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  15.         bubbleSort(arr); 
  16.         System.out.println(Arrays.toString(arr)); 
  17.     } 
  18.     public static void bubbleSort(int[] arr) { 
  19.         if (arr == null && arr.length < 2) { 
  20.             return
  21.         } 
  22.         boolean isSwap; 
  23.         // 外层循环控制比较的轮数 
  24.         for (int i = 0; i < arr.length - 1; i++) { 
  25.             isSwap = false
  26.             // 内层循环进行相邻元素比较 
  27.             for (int j = 0; j < arr.length - 1 - i; j++) { 
  28.                 if (arr[j] > arr[j + 1]) { // 前面的数比后面的数大则交换 
  29.                     swap(arr, j, j + 1); 
  30.                     isSwap = true
  31.                 } 
  32.             } 
  33.  
  34.             if (!isSwap) { 
  35.                 return; // 代码优化,如果一轮循环中没有进行交换元素则说明数组已经是有序的了 
  36.             } 
  37.         } 
  38.     } 
  39.  
  40.     /** 
  41.      * 两数交换 不用第三个数 
  42.      * 
  43.      * @param arr 
  44.      * @param i 
  45.      * @param j 
  46.      */ 
  47.     public static void swap(int[] arr, int i, int j) { 
  48.         arr[i] = arr[i] ^ arr[j]; 
  49.         arr[j] = arr[i] ^ arr[j]; 
  50.         arr[i] = arr[i] ^ arr[j]; 
  51.     } 
  52.  
  53. //    public static void swap(int[] arr, int i, int j) { 
  54. //        int temp = arr[i]; 
  55. //        arr[i] = arr[j]; 
  56. //        arr[j] = temp
  57. //    } 
  58.  
  59. //    public static void swap(int[] arr, int a, int b) { 
  60. //        arr[a] = arr[a] + arr[b]; 
  61. //        arr[b] = arr[a] - arr[b]; 
  62. //        arr[a] = arr[a] - arr[b]; 
  63. //    } 

2. 插入排序

插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

思路:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. /** 
  4.  * @date Created by 邵桐杰 on 2021/10/29 21:56 
  5.  * @微信公众号 程序员千羽 
  6.  * @个人网站 www.nateshao.cn 
  7.  * @博客 https://nateshao.gitee.io 
  8.  * @GitHub https://github.com/nateshao 
  9.  * @Gitee https://gitee.com/nateshao 
  10.  * Description: 
  11.  */ 
  12. public class Code_02_InsertionSortTest { 
  13.     public static void main(String[] args) { 
  14.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  15.         insertionSort(arr); 
  16.         for (int i = 0; i < arr.length; i++) { 
  17.             System.out.print(arr[i] + " "); 
  18.         } 
  19.     } 
  20.  
  21.     public static void insertionSort(int[] arr) { 
  22.         if (arr == null || arr.length < 2) { 
  23.             return
  24.         } 
  25.         for (int i = 1; i < arr.length; i++) { 
  26.             for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { 
  27.                 swap(arr, j, j + 1); 
  28.             } 
  29.         } 
  30.     } 
  31.  
  32.     public static void swap(int[] arr, int i, int j) { 
  33.         int temp = arr[i]; 
  34.         arr[i] = arr[j]; 
  35.         arr[j] = temp
  36.     } 

3. 希尔排序

希尔排序是对插入排序的改良。

插入排序对已经基本有序的数组排序效率高,但在移动元素时只能是相邻的两个元素交换希尔排序加了一个间隔使交换元素不限于相邻。间隔逐渐缩小当缩小到1时数组已经基本有序。

当间隔缩小时不会破坏大间隔的排序

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. /** 
  6.  * @date Created by 邵桐杰 on 2021/10/30 10:44 
  7.  * @微信公众号 程序员千羽 
  8.  * @个人网站 www.nateshao.cn 
  9.  * @博客 https://nateshao.gitee.io 
  10.  * @GitHub https://github.com/nateshao 
  11.  * @Gitee https://gitee.com/nateshao 
  12.  * Description: 希尔排序 
  13.  */ 
  14. public class Code_04_ShellSort { 
  15.     public static void main(String[] args) { 
  16.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  17.         shellSort(arr); 
  18.         System.out.println(Arrays.toString(arr)); 
  19.         shellSort1(arr); 
  20.         System.out.println(Arrays.toString(arr)); 
  21.     } 
  22.  
  23.     /** 
  24.      * 希尔排序 针对有序序列在插入时采用交换法 
  25.      * 
  26.      * @param arr 
  27.      */ 
  28.     public static void shellSort(int[] arr) { 
  29.         if (arr == null && arr.length < 2) { 
  30.             return
  31.         } 
  32.         //增量gap,并逐步缩小增量 
  33.         for (int gap = arr.length / 2; gap > 0; gap /= 2) { 
  34.             //从第gap个元素,逐个对其所在组进行直接插入排序操作 
  35.             for (int i = gap; i < arr.length; i++) { 
  36.                 int j = i; 
  37.                 while (j - gap >= 0 && arr[j] < arr[j - gap]) { 
  38.                     //插入排序采用交换法 
  39.                     swap(arr, j, j - gap); 
  40.                     j -= gap; 
  41.                 } 
  42.             } 
  43.         } 
  44.     } 
  45.  
  46.     /** 
  47.      * 希尔排序 针对有序序列在插入时采用移动法。 
  48.      * 
  49.      * @param arr 
  50.      */ 
  51.     public static void shellSort1(int[] arr) { 
  52.         if (arr == null && arr.length < 2) { 
  53.             return
  54.         } 
  55.         //增量gap,并逐步缩小增量 
  56.         for (int gap = arr.length / 2; gap > 0; gap /= 2) { 
  57.             //从第gap个元素,逐个对其所在组进行直接插入排序操作 
  58.             for (int i = gap; i < arr.length; i++) { 
  59.                 int j = i; 
  60.                 int temp = arr[j]; 
  61.                 if (arr[j] < arr[j - gap]) { 
  62.                     while (j - gap >= 0 && temp < arr[j - gap]) { 
  63.                         //移动法 
  64.                         arr[j] = arr[j - gap]; 
  65.                         j -= gap; 
  66.                     } 
  67.                     arr[j] = temp
  68.                 } 
  69.             } 
  70.         } 
  71.     } 
  72.  
  73.     public static void swap(int[] arr, int a, int b) { 
  74.         arr[a] = arr[a] + arr[b]; 
  75.         arr[b] = arr[a] - arr[b]; 
  76.         arr[a] = arr[a] - arr[b]; 
  77.  
  78.     } 

4. 选择排序

时间复杂度 ,额外空间复杂度

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

思路:

寻找出数组中最小(大)的一个数

将最小的数与当前遍历数组的首个元素交换

缩小遍历数组范围重复前两步

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. /** 
  4.  * @date Created by 邵桐杰 on 2021/10/29 22:51 
  5.  * @微信公众号 程序员千羽 
  6.  * @个人网站 www.nateshao.cn 
  7.  * @博客 https://nateshao.gitee.io 
  8.  * @GitHub https://github.com/nateshao 
  9.  * @Gitee https://gitee.com/nateshao 
  10.  * Description: 选择排序 
  11.  */ 
  12. public class Code_03_SelectionSortTest { 
  13.     public static void main(String[] args) { 
  14.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  15.         selectionSort(arr); 
  16.         for (int i = 0; i < arr.length; i++) { 
  17.             System.out.print(arr[i] + " "); 
  18.         } 
  19.     } 
  20.  
  21.     public static void selectionSort(int[] arr) { 
  22.         int min
  23.         for (int i = 0; i < arr.length - 1; i++) { 
  24.             min = i; // 默认当前排序数组的第一个元素为最小值 
  25.             for (int j = i + 1; j < arr.length; j++) { 
  26.                 if (arr[min] > arr[j]) { // 发现一个更小的,更小最小值索引 
  27.                     min = j; 
  28.                 } 
  29.             } 
  30.  
  31.             if (i != min) { 
  32.                 swap(arr, i, min); 
  33.             } 
  34.         } 
  35.     } 
  36.  
  37.     public static void swap(int[] arr, int i, int j) { 
  38.         int temp = arr[i]; 
  39.         arr[i] = arr[j]; 
  40.         arr[j] = temp
  41.     } 

5. 快速排序

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

思路:

  1. 将左边第一个数做为基准值
  2. 从右边向左遍历寻找出一个小于基准数的值,将其赋值给左边的位置
  3. 从左边向右遍历寻找出一个小于基准数的值,将其赋值给右边的位置
  4. 重复2,3 直到左右索引相遇
  5. 将基准值放在相遇的这个坐标上
  6. 递归基准值左右两边的子数组
  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. /** 
  6.  * @date Created by 邵桐杰 on 2021/10/30 12:09 
  7.  * @微信公众号 程序员千羽 
  8.  * @个人网站 www.nateshao.cn 
  9.  * @博客 https://nateshao.gitee.io 
  10.  * @GitHub https://github.com/nateshao 
  11.  * @Gitee https://gitee.com/nateshao 
  12.  * Description: 快速排序 
  13.  */ 
  14. public class Code_05_QuickSort { 
  15.     public static void main(String[] args) { 
  16.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  17.         quickSort(arr, 0, arr.length - 1); 
  18.         System.out.println(Arrays.toString(arr)); 
  19.     } 
  20.  
  21.     /** 
  22.      * @param arr        待排序列 
  23.      * @param leftIndex  待排序列起始位置 
  24.      * @param rightIndex 待排序列结束位置 
  25.      */ 
  26.     private static void quickSort(int[] arr, int leftIndex, int rightIndex) { 
  27.         /** 
  28.          * 如果左边大于右边,直接返回 
  29.          */ 
  30.         if (leftIndex >= rightIndex) { 
  31.             return
  32.         } 
  33.  
  34.         int left = leftIndex; 
  35.         int right = rightIndex; 
  36.         //待排序的第一个元素作为基准值 
  37.         int key = arr[left]; 
  38.  
  39.         //从左右两边交替扫描,直到left = right 
  40.         while (left < right) { 
  41.             while (right > left && arr[right] >= key) { 
  42.                 //从右往左扫描,找到第一个比基准值小的元素 
  43.                 right--; 
  44.             } 
  45.  
  46.             //找到这种元素将arr[right]放入arr[left]中 
  47.             arr[left] = arr[right]; 
  48.  
  49.             while (left < right && arr[left] <= key) { 
  50.                 //从左往右扫描,找到第一个比基准值大的元素 
  51.                 left++; 
  52.             } 
  53.  
  54.             //找到这种元素将arr[left]放入arr[right]中 
  55.             arr[right] = arr[left]; 
  56.         } 
  57.         //基准值归位 
  58.         arr[left] = key
  59.         //对基准值左边的元素进行递归排序 
  60.         quickSort(arr, leftIndex, left - 1); 
  61.         //对基准值右边的元素进行递归排序。 
  62.         quickSort(arr, right + 1, rightIndex); 
  63.     } 

6. 归并排序

时间复杂度,额外空间复杂度

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。

思路:

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

设定两个指针,最初位置分别为两个已经排序序列的起始位置

比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针到达序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

举例子:

比如:A数组:[4,6,9] B数组:[3,2,1]

先对数组A,B;数组A,B排好序 。定义一个新的数组C。比较A数组,小的放在前面。数组A排好序之后,将数组B复制到数组C

C数组:[1,2,3,4,6,9 ]

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. /** 
  6.  * @date Created by 邵桐杰 on 2021/10/30 12:15 
  7.  * @微信公众号 程序员千羽 
  8.  * @个人网站 www.nateshao.cn 
  9.  * @博客 https://nateshao.gitee.io 
  10.  * @GitHub https://github.com/nateshao 
  11.  * @Gitee https://gitee.com/nateshao 
  12.  * Description: 
  13.  */ 
  14. public class Code_06_MergeSort { 
  15.     public static void main(String[] args) { 
  16.         int testTime = 500000; 
  17.         int maxSize = 20; 
  18.         int maxValue = 100; 
  19.         boolean succeed = true
  20.         for (int i = 0; i < testTime; i++) { 
  21.             int[] arr1 = generateRandomArray(maxSize, maxValue); 
  22.             int[] arr2 = copyArray(arr1); 
  23.             mergeSort(arr1); 
  24.             comparator(arr2); 
  25.             if (!isEqual(arr1, arr2)) { 
  26.                 succeed = false
  27.                 printArray(arr1); 
  28.                 printArray(arr2); 
  29.                 break; 
  30.             } 
  31.         } 
  32.         System.out.println(succeed ? "Nice!" : "Fucking fucked!"); 
  33.  
  34.         int[] arr = generateRandomArray(maxSize, maxValue); 
  35.         printArray(arr); 
  36.         mergeSort(arr); 
  37.         printArray(arr); 
  38.  
  39.     } 
  40.     /** 
  41.      * 如果数组的长度为空,或者是数组的长度为1。直接返回,不需要比较 
  42.      * @param arr 
  43.      */ 
  44.     public static void mergeSort(int[] arr) { 
  45.         if (arr == null || arr.length < 2) { 
  46.             return
  47.         } 
  48.         mergeSort(arr, 0, arr.length - 1); 
  49.     } 
  50.  
  51.     /** 
  52.      * 这个范围上只有一个数,直接返回 
  53.      * @param arr 
  54.      * @param l 
  55.      * @param r 
  56.      */ 
  57.     public static void mergeSort(int[] arr, int l, int r) { 
  58.         if (l == r) { 
  59.             return
  60.         } 
  61.         int mid = l + ((r - l) >> 1); // L和R中点的位置 (L + R)/ 2 
  62.         mergeSort(arr, l, mid); // 左部分排序  T(n/2) 
  63.         mergeSort(arr, mid + 1, r); // 右部分排序 T(n/2) 
  64.         merge(arr, l, mid, r); // 左部分和右部分合并 O(n) 
  65.         // 总的时间复杂度:T(N) = 2T(n/2) + O(N) 
  66.     } 
  67.  
  68.     public static void merge(int[] arr, int l, int m, int r) { 
  69.         int[] help = new int[r - l + 1]; 
  70.         int i = 0; 
  71.         int p1 = l;// 数组l,左侧第一的最小值。 
  72.         int p2 = m + 1;// 右侧第一的最小值。 
  73.         while (p1 <= m && p2 <= r) { // p1 or p2 谁小取谁,放在新的数组,重新排序数组 
  74.             help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; 
  75.         } 
  76.         /** 
  77.          * p1,p2两个数,必有一个数越界 
  78.          */ 
  79.         while (p1 <= m) { 
  80.             help[i++] = arr[p1++]; 
  81.         } 
  82.         while (p2 <= r) { 
  83.             help[i++] = arr[p2++]; 
  84.         } 
  85.         for (i = 0; i < help.length; i++) { 
  86.             arr[l + i] = help[i]; // 最后将数组拷贝到arr[i] 
  87.         } 
  88.     } 
  89.  
  90.     /** 
  91.      * 比较器 
  92.      * @param arr 
  93.      */ 
  94.     public static void comparator(int[] arr) { 
  95.         Arrays.sort(arr); 
  96.     } 
  97.  
  98.     /** 
  99.      * 生成任意数组 
  100.      * @param maxSize 
  101.      * @param maxValue 
  102.      * @return 
  103.      */ 
  104.     public static int[] generateRandomArray(int maxSize, int maxValue) { 
  105.         int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; 
  106.         for (int i = 0; i < arr.length; i++) { 
  107.             arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); 
  108.         } 
  109.         return arr; 
  110.     } 
  111.  
  112.     /** 
  113.      * 复制数组 
  114.      * @param arr 
  115.      * @return 
  116.      */ 
  117.     public static int[] copyArray(int[] arr) { 
  118.         if (arr == null) { 
  119.             return null
  120.         } 
  121.         int[] res = new int[arr.length]; 
  122.         for (int i = 0; i < arr.length; i++) { 
  123.             res[i] = arr[i]; 
  124.         } 
  125.         return res; 
  126.     } 
  127.  
  128.     /** 
  129.      * 两数组是否相等 
  130.      * @param arr1 
  131.      * @param arr2 
  132.      * @return 
  133.      */ 
  134.     public static boolean isEqual(int[] arr1, int[] arr2) { 
  135.         if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { 
  136.             return false
  137.         } 
  138.         if (arr1 == null && arr2 == null) { 
  139.             return true
  140.         } 
  141.         if (arr1.length != arr2.length) { 
  142.             return false
  143.         } 
  144.         for (int i = 0; i < arr1.length; i++) { 
  145.             if (arr1[i] != arr2[i]) { 
  146.                 return false
  147.             } 
  148.         } 
  149.         return true
  150.     } 
  151.  
  152.     /** 
  153.      * 打印数组 
  154.      * @param arr 
  155.      */ 
  156.     public static void printArray(int[] arr) { 
  157.         if (arr == null) { 
  158.             return
  159.         } 
  160.         for (int i = 0; i < arr.length; i++) { 
  161.             System.out.print(arr[i] + " "); 
  162.         } 
  163.         System.out.println(); 
  164.     } 

7. 计数排序

计数排序是桶排序的一种。是一个非基于比较的排序算法。

它的优势在于在对一定范围内的整数排序时,它的复杂度为(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当 的时候其效率反而不如基于比较的排序

思路:

  1. 找出数组中最大最小值
  2. 创建对应长度的数组(max - min +1)
  3. 统计每个数出现的次数 array[val - min]++
  4. 填充目标数组

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. /** 
  6.  * @date Created by 邵桐杰 on 2021/10/30 14:38 
  7.  * @微信公众号 程序员千羽 
  8.  * @个人网站 www.nateshao.cn 
  9.  * @博客 https://nateshao.gitee.io 
  10.  * @GitHub https://github.com/nateshao 
  11.  * @Gitee https://gitee.com/nateshao 
  12.  * Description: 计数排序 
  13.  * 原文链接:https://baijiahao.baidu.com/s?id=1613842852280800384&wfr=spider&for=pc 
  14.  */ 
  15. public class Code_07_CountSort { 
  16.  
  17.     public static void main(String[] args) { 
  18.         int[] array = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  19.         int[] sortedArray = countSort(array); 
  20.         System.out.println(Arrays.toString(sortedArray)); 
  21.     } 
  22.  
  23.     public static int[] countSort(int[] array) { 
  24.         //1.得到数列的最大值和最小值,并算出差值 
  25.         int max = array[0]; 
  26.         int min = array[0]; 
  27.         for (int i = 1; i < array.length; i++) { 
  28.             if (array[i] > max) { 
  29.                 max = array[i]; 
  30.             } 
  31.             if (array[i] < min) { 
  32.                 min = array[i]; 
  33.             } 
  34.         } 
  35.         int d = max - min
  36.         //2.创建统计数组并统计对应元素个数 
  37.         int[] countArray = new int[d + 1]; 
  38.         for (int i = 0; i < array.length; i++) { 
  39.             countArray[array[i] - min]++; 
  40.         } 
  41.         //3.统计数组做变形,后面的元素等于前面的元素之和 
  42.         int sum = 0; 
  43.         for (int i = 0; i < countArray.length; i++) { 
  44.             sum += countArray[i]; 
  45.             countArray[i] = sum
  46.         } 
  47.         //4.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组 
  48.         int[] sortedArray = new int[array.length]; 
  49.         for (int i = array.length - 1; i >= 0; i--) { 
  50.             sortedArray[countArray[array[i] - min] - 1] = array[i]; 
  51.             countArray[array[i] - min]--; 
  52.         } 
  53.         return sortedArray; 
  54.     } 

计数排序的局限性:

1.当数列最大最小值差距过大时,并不适用计数排序。

比如给定20个随机整数,范围在0到1亿之间,这时候如果使用计数排序,需要创建长度1亿的数组。不但严重浪费空间,而且时间复杂度也随之升高。

2.当数列元素不是整数,并不适用计数排序。

如果数列中的元素都是小数,比如25.213,或是0.00000001这样子,则无法创建对应的统计数组。这样显然无法进行计数排序。

8. 基数排序

基数排序是桶排序的一种。原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。每位比较完成后将数组重新排序。当最高位完成比较时,数组也就变成了一个有序序列。

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. /** 
  6.  * @date Created by 邵桐杰 on 2021/10/30 15:15 
  7.  * @微信公众号 程序员千羽 
  8.  * @个人网站 www.nateshao.cn 
  9.  * @博客 https://nateshao.gitee.io 
  10.  * @GitHub https://github.com/nateshao 
  11.  * @Gitee https://gitee.com/nateshao 
  12.  * Description: 基数排序 
  13.  * 原文链接:https://blog.csdn.net/weixin_44537194/article/details/87302788 
  14.  */ 
  15. public class Code_08_RadixSort { 
  16.     public static void main(String[] args) { 
  17.         int[] array = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  18.         radixSort(array); 
  19.         System.out.println(Arrays.toString(array)); 
  20.     } 
  21.  
  22.     public static void radixSort(int[] arr) { 
  23.         // 存数组中最大的数字,为了知道循环几次 
  24.         int max = Integer.MIN_VALUE;// (整数中的最小数) 
  25.         // 遍历数组,找出最大值 
  26.         for (int i = 0; i < arr.length; i++) { 
  27.             if (max < arr[i]) { 
  28.                 max = arr[i]; 
  29.             } 
  30.         } 
  31.  
  32.         // 计算最大数是几位数,,此方法计较绝妙 
  33.         int maxLength = (max + "").length(); 
  34.         // 用于临时存储数据的数组 
  35.         int[][] temp = new int[10][arr.length]; 
  36.         // 用于存储桶内的元素位置 
  37.         int[] counts = new int[arr.length]; 
  38.  
  39.         // 第一轮个位数较易得到余数,第二轮就得先除以十再去取余,之后百位除以一百 
  40.         // 可以看出,还有一个变量随循环次数变化,为了取余 
  41.  
  42.         // 循环的次数 
  43.         for (int i = 0, n = 1; i < maxLength; i++, n *= 10) { 
  44.             // 每一轮取余 
  45.             for (int j = 0; j < arr.length; j++) { 
  46.                 // 计算余数 
  47.                 int ys = (arr[j] / n) % 10; 
  48.                 // 把便利店数据放在指定数组中,有两个信息,放在第几个桶+数据应该放在第几位 
  49.                 temp[ys][counts[ys]] = arr[j]; 
  50.                 // 记录数量 
  51.                 counts[ys]++; 
  52.             } 
  53.  
  54.             // 记录取的数字应该放到位置 
  55.             int index = 0; 
  56.             // 每一轮循环之后把数字取出来 
  57.             for (int k = 0; k < counts.length; k++) { 
  58.                 // 记录数量的数组中当前余数记录不为零 
  59.                 if (counts[k] != 0) { 
  60.                     for (int l = 0; l < counts[k]; l++) { 
  61.                         // 取出元素 
  62.                         arr[index] = temp[k][l]; 
  63.                         index++; 
  64.                     } 
  65.                     // 取出后把数量置为零 
  66.                     counts[k] = 0; 
  67.                 } 
  68.             } 
  69.         } 
  70.     } 

9. 桶排序

桶排序(Bucket sort) 或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序。

思路:

  1. 设置一个定量的数组当作空桶子。
  2. 寻访序列,并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的序列中。

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_01_ten_sort; 
  2.  
  3. import java.util.ArrayList; 
  4. import java.util.Arrays; 
  5. import java.util.List; 
  6. import java.util.Random; 
  7.  
  8. /** 
  9.  * @date Created by 邵桐杰 on 2021/10/30 15:56 
  10.  * @微信公众号 程序员千羽 
  11.  * @个人网站 www.nateshao.cn 
  12.  * @博客 https://nateshao.gitee.io 
  13.  * @GitHub https://github.com/nateshao 
  14.  * @Gitee https://gitee.com/nateshao 
  15.  * Description: 桶排序 
  16.  */ 
  17. public class Code_09_BucketSort { 
  18.  
  19.     public static void main(String[] args) { 
  20.         int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; 
  21.         bucketSort(arr); 
  22.         System.out.println(Arrays.toString(arr)); 
  23.     } 
  24.  
  25.     public static void bucketSort(int[] arr) { 
  26.         int max = arr[0]; 
  27.         int min = arr[0]; 
  28.         for (int i = 1; i < arr.length; i++) { 
  29.             if (max < arr[i]) { 
  30.                 max = arr[i]; 
  31.             } 
  32.             if (min > arr[i]) { 
  33.                 min = arr[i]; 
  34.             } 
  35.         } 
  36.         int bucketNum = (max - min) / 10 + 1; // 桶的数量 
  37.         List[] buckets = new ArrayList[bucketNum]; 
  38.         for (int i = 0; i < buckets.length; i++) { 
  39.             buckets[i] = new ArrayList<Integer>(); 
  40.         } 
  41.  
  42.         // 把元素装入桶中 
  43.         for (int i = 0; i < arr.length; i++) { 
  44.             int a = (arr[i] - min) / 10; 
  45.             buckets[a].add(arr[i]); 
  46.         } 
  47.  
  48.         int index = 0; 
  49.         for (List<Integer> b : buckets) { 
  50.             insertionSort(b); // 排序桶内元素 
  51.             for (Integer num : b) { 
  52.                 arr[index++] = num; // 取出排序好的元素 
  53.             } 
  54.         } 
  55.     } 
  56.  
  57.     // 插入排序 
  58.     public static void insertionSort(List<Integer> arr) { 
  59.         for (int i = 1; i < arr.size(); i++) { 
  60.             // 从第二个开始查看是否比前一个小 
  61.             if (arr.get(i) < arr.get(i - 1)) { 
  62.                 // 逐个往前找如果大于此数则将其往后移动一位 
  63.                 int temp = arr.get(i); 
  64.                 int j = i - 1; 
  65.                 while (j >= 0 && temp < arr.get(j)) { 
  66.                     arr.set(j + 1, arr.get(j)); 
  67.                     j--; 
  68.                 } 
  69.                 // 找到一个比它小的数----将其赋值给此数后面的元素 
  70.                 arr.set(j + 1, temp); 
  71.             } 
  72.         } 
  73.     } 

10. 堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆是一个完全二叉树,完全二叉树就是节点从上往下从左往右中间不会有缺失的。简单点说就是一个节点如果存在右子树那么左子树也一定存在

手撕十大经典排序算法

堆排序描述:

手撕十大经典排序算法

上图中在3这个节点有右子树却没有左子树,所以这不是一个完全二叉树。

堆还分为大顶堆和小顶堆

  • 大顶堆 父节点值大于或等于子节点
  • 小顶堆 父节点值小于或等于子节点

手撕十大经典排序算法

如果我们把一个数组上的元素都映射到一个堆上可以发现他们的index有着如下的关系左节点index = 父节点 * 2 + 1 右节点index = 父节点 * 2 + 2

手撕十大经典排序算法

对于一个大顶堆,根节点就是它的最大值。我们只需要根据上面的关系进行交换父子节点的元素,就可以将一个随机的数组转化为堆。然后将根节点和最后的节点交换位置。然后把缩小数组长度。在对这个数组进行堆的转化,同时又可以获取到该数组最大的数,在移动到数组最后面。在进行堆的转化…

思路:

将整个数组构建成堆

交换根节点和最后一个节点

从根节点开始heapify

重复2,3直到遍历所有元素

手撕十大经典排序算法

代码实现:

  1. package com.nateshao.basic_class_01; 
  2.  
  3. import java.util.Arrays; 
  4.  
  5. public class Code_03_HeapSort { 
  6.  
  7.     public static void main(String[] args) { 
  8.         int testTime = 500000; 
  9.         int maxSize = 20; 
  10.         int maxValue = 100; 
  11.         boolean succeed = true
  12.         for (int i = 0; i < testTime; i++) { 
  13.             int[] arr1 = generateRandomArray(maxSize, maxValue); 
  14.             int[] arr2 = copyArray(arr1); 
  15.             heapSort(arr1); 
  16.             comparator(arr2); 
  17.             if (!isEqual(arr1, arr2)) { 
  18.                 succeed = false
  19.                 break; 
  20.             } 
  21.         } 
  22.         System.out.println(succeed ? "Nice!" : "Fucking fucked!"); 
  23.  
  24.         int[] arr = generateRandomArray(maxSize, maxValue); 
  25.         printArray(arr); 
  26.         heapSort(arr); 
  27.         printArray(arr); 
  28.     } 
  29.  
  30.     /** 
  31.      * 堆排序 
  32.      * @param arr 
  33.      */ 
  34.     public static void heapSort(int[] arr) { 
  35.         if (arr == null || arr.length < 2) { 
  36.             return
  37.         } 
  38.         // 建立大根堆 
  39.         for (int i = 0; i < arr.length; i++) { 
  40.             heapInsert(arr, i); 
  41.         } 
  42.         int size = arr.length; 
  43.         swap(arr, 0, --size); // 最后一位数,与第一位数交换。 堆大小减1 
  44.         while (size > 0) { 
  45.             heapify(arr, 0, size); // 继续调整大根堆 
  46.             swap(arr, 0, --size);      // 继续。最后一位数,与第一位数交换。 堆大小减1 
  47.         } 
  48.     } 
  49.  
  50.     /** 
  51.      * 建立大根堆 
  52.      * 
  53.      * @param arr 
  54.      * @param index 
  55.      */ 
  56.     public static void heapInsert(int[] arr, int index) { 
  57.         while (arr[index] > arr[(index - 1) / 2]) { 
  58.             swap(arr, index, (index - 1) / 2); 
  59.             index = (index - 1) / 2; 
  60.         } 
  61.     } 
  62.  
  63.     public static void heapify(int[] arr, int indexint size) { 
  64.         int left = index * 2 + 1; // 左孩子。i*2+1    右孩子:left + 1 
  65.         while (left < size) { // 堆大小 
  66.             // 左孩子有孩子比较,谁的值大就取谁 
  67.             int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left
  68.             // 节点与左右孩子比较,谁大取谁下标 
  69.             largest = arr[largest] > arr[index] ? largest : index
  70.             // 是我自己,就不用往下执行 
  71.             if (largest == index) { 
  72.                 break; 
  73.             } 
  74.             swap(arr, largest, index); // largest != index 
  75.             index = largest; 
  76.             left = index * 2 + 1; 
  77.         } 
  78.     } 
  79.  
  80.     public static void swap(int[] arr, int i, int j) { 
  81.         int tmp = arr[i]; 
  82.         arr[i] = arr[j]; 
  83.         arr[j] = tmp; 
  84.     } 
  85.  
  86.     public static void comparator(int[] arr) { 
  87.         Arrays.sort(arr); 
  88.     } 
  89.  
  90.     public static int[] generateRandomArray(int maxSize, int maxValue) { 
  91.         int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; 
  92.         for (int i = 0; i < arr.length; i++) { 
  93.             arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); 
  94.         } 
  95.         return arr; 
  96.     } 
  97.  
  98.     public static int[] copyArray(int[] arr) { 
  99.         if (arr == null) { 
  100.             return null
  101.         } 
  102.         int[] res = new int[arr.length]; 
  103.         for (int i = 0; i < arr.length; i++) { 
  104.             res[i] = arr[i]; 
  105.         } 
  106.         return res; 
  107.     } 
  108.  
  109.     public static boolean isEqual(int[] arr1, int[] arr2) { 
  110.         if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { 
  111.             return false
  112.         } 
  113.         if (arr1 == null && arr2 == null) { 
  114.             return true
  115.         } 
  116.         if (arr1.length != arr2.length) { 
  117.             return false
  118.         } 
  119.         for (int i = 0; i < arr1.length; i++) { 
  120.             if (arr1[i] != arr2[i]) { 
  121.                 return false
  122.             } 
  123.         } 
  124.         return true
  125.     } 
  126.  
  127.     public static void printArray(int[] arr) { 
  128.         if (arr == null) { 
  129.             return
  130.         } 
  131.         for (int i = 0; i < arr.length; i++) { 
  132.             System.out.print(arr[i] + " "); 
  133.         } 
  134.         System.out.println(); 
  135.     } 

总结

以上就是经典的十大排序算法了。实际上,你去面试某公司,上来啥都不问,就考你算法。但是在工作上,算法几乎用不到。

但是没办法,现实就是这样。当然算法是作为一名合格程序员所要掌握的最基本的知识,一线开发岗位,包括架构师和 Leader,只要还在写代码,那么面试中必然是要包括算法这部分内容的。

参考链接:

  • 排序的定义:https://blog.csdn.net/weixin_41190227/article/details/86600821
  • 十大经典排序算法:https://zfhelo.gitee.io/2020/06/14/1
  • 希尔排序:https://www.cnblogs.com/chengxiao/p/6104371.html
  • 计数排序:https://baijiahao.baidu.com/s?id=1613842852280800384&wfr=spider&for=pc
  • 基数排序:https://blog.csdn.net/weixin_44537194/article/details/87302788

原文链接:https://mp.weixin.qq.com/s/mb0gMPMRxh3tsQQ3NnhOfw

延伸 · 阅读

精彩推荐
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5972021-03-08
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5232021-06-21
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20