首页 > 编程语言 >图解排序算法,这五种最热门!

图解排序算法,这五种最热门!

时间:2022-12-20 20:36:09浏览次数:62  
标签:arr 排序 temp int 这五种 数组 图解 left

介绍 5 种热门的排序算法,用图文 + 代码的方式讲解~

说到排序算法,大家估计都比较熟悉,但要你一下子写出来又蒙圈了。所以这篇文章不会讲解所有的排序算法,而是挑选最热门的五种:冒泡排序、选择排序、插入排序、快速排序、归并排序。

我们通过​​图文 + 流程解释​​ 的方式,让大家能快速领悟到各个排序算法的思想,从而达到快速掌握的目的。此外每个排序算法都有对应的 Github 代码实现,可供大家调试理解算法。同时也附上了文章中所画图的 draw.io 数据文件,方便大家根据自己的习惯进行修改。

排序算法的仓库地址:​​java-code-chip/src/main/java/tech/shuyi/javacodechip/sort at master · chenyurong/java-code-chip​

如果你已经不是第一次学习排序算法,那么我建议你按照这样的思路学习:

  1. 通过图解或调试,弄清楚每个算法的思想。
  2. 下载 Github 例子,尝试自己手写实现。
  3. 定期复习手写实现,不断巩固知识点。

好了,废话不多说,让我们开始今天的图解排序算法吧!

选择排序

选择排序,意思是每次从待排序的元素选出极值作为首元素,直到所有元素排完为止。 其详细的排序逻辑如下图所示:

  1. 第 1 次,index 下标对应值为 9,找出所有最小值为 1,将 9 与 1 交换位置,得到 ②。同时,index 下标加一。
  2. 第 2 次,index 下标对应值为 3,找出所有最小值为 3,将 3 与 2 交换位置,得到 ③。同时,index 下标加一。
  3. 第 3 次,index 下标对应值为 9,找出所有最小值为 3,将 9 与 3 交换位置,得到 ④。同时,index 下标加一。
  4. 一直这样循环下去,直到 index 下标到达数组边界,如 ⑥ 所示。

注意:灰色部分表示已经完成排序的部分。

选择排序的算法比较简单,如下所示:

public static void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = i;//每一趟循环比较时,min用于存放较小元素的数组下标,这样当前批次比较完毕最终存放的就是此趟内最小的元素的下标,避免每次遇到较小元素都要进行交换。
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
//进行交换,如果min发生变化,则进行交换
if (min != i) {
swap(arr,min,i);
}
}
}

可调式代码地址:​​java-code-chip/SelectSort.java at master · chenyurong/java-code-chip​

简单选择排序通过上面优化之后,无论数组原始排列如何,比较次数是不变的;对于交换操作,在最好情况下也就是数组完全有序的时候,无需任何交换移动,在最差情况下,也就是数组倒序的时候,交换次数为n-1次。综合下来,时间复杂度为O(n2)。

冒泡排序

冒泡排序,就是像池塘里的水泡一样往上冒泡。我们可以理解成一个数不断地往上冒泡(比较交换),一直到最上面(末尾)。通过不断往上冒泡,每次冒泡都会将最值浮到最上层,最终达到完全有序。 其详细的排序算法逻辑如下:

  1. 第 1 轮,9 大于 3,那么将 9 与 3 交换,接着继续往下比较。9 大于 1,那么将 9 与 1 交换,接着往下比较,最终我们将 9 浮到数组顶端。此时 index 指向数组顶端,该数是有序的了,因此 index 减一。
  2. 第 2 轮,3 大于 1,那么 3 与 1 交换,接着往下比较。最终,我们只需要比较到 index 位置即可,最终我们将 7 浮到数组顶端。同时 index 也减一,此时 7、9 是有序的。
  3. 如此这样反复循环,直到 index 下标达到 0 即可。

在冒泡排序的过程中,如果某一趟执行完毕,没有做任何一次交换操作,那么就说明剩下的序列已经是有序的了。例如数组[5,4,1,2,3],执行了两次冒泡之后,其数组变为 [1,2,3,4,5]。此时,index 下标指向 3 这个值。再执行第三次冒泡时,我们会发现 1<2<3,我们一次交换都没有做,这就说明剩下的序列已经是有序的,排序操作已经完成,不需要再进行排序了。

public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = true;//设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已然完成。
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr,j,j+1);
flag = false;
}
}
if (flag) {
break;
}
}
}

可调试代码地址:​​java-code-chip/BubbleSort.java at master · chenyurong/java-code-chip​

插入排序

插入排序,即将元素一个个插入新的数组系列中,直到所有元素插完为止。 例如下图的例子,第 1 次将元素 9 插入新的数组中

/**
* 插入排序
*
* @param arr
*/
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int j = i;
while (j > 0 && arr[j - 1] > arr[j]) {
swap(arr,j,j-1);
j--;
}
}
}

可调式代码地址:​​java-code-chip/InsertSort.java at master · chenyurong/java-code-chip​

简单插入排序在最好情况下,需要比较n-1次,无需交换元素,时间复杂度为O(n);在最坏情况下,时间复杂度依然为O(n2)。但是在数组元素随机排列的情况下,插入排序还是要优于上面两种排序的。

快速排序

快速排序,顾名思义其排序效率非常高,所以才叫快速排序。快速排序的核心思想是选取一个基准数,通过一趟排序将小于此基准数的数字放到左边,将大于此基准数的数字放到右边。之后再用遍历不断地对左右子串进行同样的操作,从而达到排序的目的。 快速排序的时间复杂度在最坏情况下是 O(N2),平均的时间复杂度是 O(N*lgN)。

例如下图中的例子,在第一趟排序里,我们选中了基准数为 9,那么此次排序就把所有比 9 小的数放到了左边,所有比 9 大的数放到了右边。在第二趟排序里,我们选中了基准数为 7,那么此次排序就把所有比 7 小的数放到了左边,所有比 7 大的数放到了右边。

我们以对 ​​9 3 1 4 2 7​​ 整数串进行排序为例,详细讲解整个快速排序的流程:

  1. 选取 9 为基准数,从 right 开始,从右到左找出第一个小于 9 的数。
  2. 第一个数是 7,小于 9,符合条件。于是将找到的这个数值放到 left 位置上,同时 left 加一。
  3. 从 left 开始,从左到右选取第一个大于 9 的数。
  4. 可以看到子串中并没有一个大于 9 的数,于是 left 会一直累加到 right 的位置。
  5. 当 left >= right 时,单趟排序结束,将基准数填入 left 所在位置。
  6. 最终整个字符串被以 9 为基准数,切割成两部分,左边部分比 9 小,右边部分比 9 大。

接着进行第二次排序,第二次排序的整体流程如下:

  1. 选取 7 为基准数,从 right 开始,从右到左找出第一个小于 9 的数。
  2. 第一个数是 2,小于 9,符合条件。于是将找到的这个数值放到 left 位置上,同时 left 加一,此时数组变为:2 3 1 4 2 9。
  3. 从 left 开始,从左到右选取第一个大于 9 的数。
  4. 可以看到子串中并没有一个大于 9 的数,于是 left 会一直累加到 right 的位置。
  5. 当 left >= right 时,单趟排序结束,将基准数填入 left 所在位置。
  6. 最终整个字符串被以 7 为基准数,切割成两部分,左边部分比 7 小,右边部分比 7 大。

剩余的子串都进行同样的处理逻辑,最终我们可以得到一个排序的整数串。

代码实现:

/**
* v
* @param arr -- 待排序的数组
* @param l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
* @param r -- 数组的右边界(例如,排序截至到数组末尾,则r=arr.length-1)
*/
public static void quickSort(int arr[], int l, int r) {
if (l < r) {
int i,j,x;

i = l;
j = r;
x = arr[i];
while (i < j)
{
// 从右向左找第一个小于x的数
while(i < j && arr[j] > x) {
j--;
}
if(i < j) {
arr[i] = arr[j];
i++;
}
// 从左向右找第一个大于x的数
while(i < j && arr[i] < x) {
i++;
}
if(i < j) {
arr[j] = arr[i];
j--;
}
}
arr[i] = x;
quickSort(arr, l, i-1);
quickSort(arr, i+1, r);
}
}

可调式代码地址:​​java-code-chip/QuickSort.java at master · chenyurong/java-code-chip​

参考:​​快速排序 - 如果天空不死

归并排序,其英文名为 Merge Sort,其意思是将排序串拆分成最小的单位之后,再一个个合并成有序的子串。 例如下图的整数串,将其拆分成最小的子串就是每个只有一个整数。之后再将每个单个的子串合并起来,例如:8 与 4 合并起来成为有序子串 ​​4、8​​​,5 与 7 合并起来成为有序子串 ​​5、7​​​。​​4、8​​​ 和 ​​5、7​​​ 再合并成为有序子串 ​​4、5、7、8​​。

可以看到在这个过程中,最关键是合并两个有序子串的算法。这里我们以 [4,5,7,8] 和 [1,2,3,6] 为例,讲解有序子串合并的算法流程。

  1. 首先声明一个与原有数组相同的长度的临时数组 temp。
  2. 接着 i 指向子串 1 开始的位置,j 指向子串 2 开始的位置。接着比较 arr1[i] 与 arr2[j] 的值,找出较小值。因为两个子串都是有序的,所以这两个值中的最小值,就是整个串中的最小值。找出最小值后将其值放入 temp 的开始位置,最小值对应的子串下标加 1。这里可以看到是 4 < 1,即子串 arr2 的值较小,那么将 1 放入 temp[0] 位置,接着 j 加一,此时 j 指向 2。
  3. 接着继续对比 i 和 j 两个数的大小,继续对比步骤 2 的逻辑。这里可以看到 arr[i]=4 < arr[j]=2,那么应该将较小值放入 temp 数组中,即将 2 放入数组中,并且将 j + 1,即 j = 2,此时 j 指向的值 为 3。
  4. 按着上述的步骤继续不断重复步骤 2 的内容,我们会看到子串 2 首先到末尾。此时子串 1 还剩下一些数值,这些数值肯定是更大的值,那么直接将这些数值复制到 temp 数组中即可。如果子串 1 先到末尾,那么就应该将子串 2 剩余的数值写入 temp 数组。
  5. 最后,将 temp 的数值写回原有数组中即可。

代码实现:

public static void sort(int []arr){
//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int []temp = new int[arr.length];
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
//左边归并排序,使得左子序列有序
sort(arr,left,mid,temp);
//右边归并排序,使得右子序列有序
sort(arr,mid+1,right,temp);
//将两个有序子数组合并操作
merge(arr,left,mid,right,temp);
}
}
private static void merge(int[] arr, int left, int mid, int right, int[] temp){
//左序列指针
int i = left;
//右序列指针
int j = mid+1;
//临时数组指针
int t = 0;
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
//将左边剩余元素填充进temp中
while(i<=mid){
temp[t++] = arr[i++];
}
//将右序列剩余元素填充进temp中
while(j<=right){
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}

可调试代码地址:​​java-code-chip/MergeSort.java at master · chenyurong/java-code-chip​

参考:​​图解排序算法(四)之归并排序 - dreamcatcher-cx 

算法对比

选择排序与冒泡排序的区别?

选择排序是每次选出最值,然后放到数组头部。而冒泡排序则是不断地两两对比,将最值放到数组尾部。本质上,他俩每次都是选出最值,然后放到一边。

其最大的不同点是:选择排序只需要做一次交换,而冒泡排序则需要两两对比交换,所以冒泡排序的效率相对来说会低一些,因为会做多一些无意义的交换操作。

快速排序与归并排序的区别?

刚刚看了一下,快速排序和归并排序,我觉得差别可以提现在拆分合并的过程中,比较的时机。

快排和归并,都是不断拆分到最细。但是归并更纯粹,拆分时不做比较,直接拆!而快排还是会比较一下的。所以在拆分阶段,快排会比归并耗时一些。

而因为快排在拆分阶段会比较,所以其拆得没有归并多层级,因此其在合并阶段就少做一些功夫,会快一些。

所以快排和归并排序的区别,本质上就是拆分、合并的区别。

参考资料

  • ​​快速排序 - 如果天空不死​​图解排序算法(四)之归并排序 - dreamcatcher-cx -

图解排序算法,这五种最热门!_数据结构与算法



标签:arr,排序,temp,int,这五种,数组,图解,left
From: https://blog.51cto.com/u_13879334/5956639

相关文章

  • C#-记录几个排序算法(选择/插入/冒泡/希尔)
     给定一组数据,使用不同的算法对其进行递增排序:int[]rawList={12,33,21,2,43,5,67,8,96,4,22,36,23,42,90};选择排序:找到最大的数值,交换在最后一......
  • 数据结构(起泡排序)
    #include<stdio.h>#defineKEYTYPEint#defineMAXSIZE100typedefstruct{KEYTYPEkey;}SSELEMENT;intCOMPARE(SSELEMENTa[],intn){inti,j,p,flag;i=n......
  • AcWing787.归并排序
    题目描述给定你一个长度为\(n\)的整数数列。请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。输入格式输入共两行,第一行包含整数\(......
  • 技术分享 | InnoDB 排序索引的构建
    作者:SatyaBodapati翻译:管长龙从MySQL5.7开始,开发人员改变了InnoDB构建二级索引的方式,采用自下而上的方法,而不是早期版本中自上而下的方法了。在这篇文章中,我们将通过......
  • 数据结构堆(Heap)&排序
    在我们描述堆之前,我们首先要明白一个概念,什么是树?树的概念及结构1.树的概念树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是......
  • 基础算法(排序 & 查找)
    快速排序、归并排序、整数二分查找、浮点数二分查找快速排序主要思想是分治:确定分界点调整范围递归处理左右两段代码:#include<iostream>usingnamespacestd;......
  • SVG 从入门到后悔,怎么不早点学起来(图解版)
    本文简介点赞+关注+收藏=学会了作为一只前端,只懂Vue、React感觉已经和大家拉不开距离了。可视化、机器学习等领域JS都有涉及到,而可视化方面已经被很多领域用......
  • [数据分析与可视化] 数据绘图要点1_注重数据排序
    date:2021-11-1410:45:06+0800tags:-数据分析与可视化-R语言数据绘图要点1-注重数据排序默认情况下,大多数数据可视化工具将使用字母顺序或使用输入表中的出......
  • 简单排序
    选择排序选择排序原理:选择一个数组中的第i个数,跟后面所有数进行比较,如果i位置数大于后面位置的数,交换,直到达到数组的末尾。时间复杂度:O(N方)代码:publicstaticvoidse......
  • 剑指offer 数字在排序数组中出现的次数(C++)
    题目描述统计一个数字在排序数组中出现的次数。代码实现classSolution{public:intGetNumberOfK(vector<int>data,intk){if(data.empty())re......