对三种基础排序算法的理解

总结下三种基础的排序算法,按自己的思路来理解的,代码不是最优的也不够简练,主要为了记录思考过程,帮助大家理解算法的基本原理。

首先说下对排序的理解,工作中很少会手写一个排序算法,用也是现成的,有些数据甚至是从数据库中直接order by出来的,复杂点也就是取个最大值,或者把几个数据项中的数据合并后按照某个指标进行排序。但为啥还要去了解这些排序算法呢?我认为是可以从中了解到一些数据的处理流程、处理成本等基本原则。这些原则是可以被应用到日常开发中的,其实越是基础的东西应用面也越广。

说起排序,第一印象肯定是“无非就是从小到大排序呗”,比如这几个数 3、2、5、4、1,一眼看到,不就是 1、2、3、4、5嘛。问我正在上小学的孩子也是这么和我说的,就是一个个排呀!如果是10个,100个,1000个呢?陷入了沉思。。。

其实这就是排序的核心,所谓“一眼看到”其实就隐含了经过各个比较,也就是比较一个个数,再按特定顺序记录下来。只不过我们要把这个比较的方法告诉计算机,让它来帮我们排(计算机最适合做这种重复的劳动)。

条条大路通罗马、殊途同归,讲的都是同一个目标有多种实现方法。下面就分析三种基础的排序方法,看看是怎么告诉计算机的。开始之前大家可以先思考下,你有啥方法来排序,是否和下面这几个有类似之处呢。

一 冒泡排序

1 从一侧开始,拿第一个元素a和其他元素依次比较,如果a小于其中一个元素,则交换两者的位置,之后再拿这个交换后的元素(即当前最小的元素)再和剩余比较,有则继续交换,直到本轮结束。
2 再从第二个元素开始按步骤1的流程来处理,直到最后一个元素处理结束。
核心是每次都要和剩余元素值进行比较,发现比当前值小就和它交换位置

来看下Java版的具体实现,建议手动敲一遍,真实体验下。运行代码后的输出里我加上了详细说明,有助于理解。

 1 import java.util.Arrays;
 2 
 3 public class BubbleSort {
 4     public static void main(String[] args) {
 5         //对这几个数排序
 6         int[] numbers = {3, 5, 4, 1, 2, 6};
 7         System.out.println("*original" + Arrays.toString(numbers));
 8 
 9         //从小到大
10         for (int i = 0; i < numbers.length; i++) {
11             System.out.print("loop " + i);
12             printArray(numbers, i, i);
13             for (int j = i + 1; j < numbers.length; j++) {
14                 System.out.print("\t");
15                 printArray(numbers, i, j);
16                 //只要当前值比剩余值大就交换两者的位置
17                 if (numbers[i] > numbers[j]) {
18                     System.out.print("\t" + numbers[i] + "<->" + numbers[j] + "");
19                     //通过中间变量来交换两者值
20                     int tmp = numbers[i];
21                     numbers[i] = numbers[j];
22                     numbers[j] = tmp;
23                     printArray(numbers, i, j);
24                 }
25             }
26         }
27         System.out.println("*sorted" + Arrays.toString(numbers));
28     }
29 
30     private static void printArray(int[] ints, int current, int compare) {
31         StringBuilder str = new StringBuilder("[");
32         for (int i = 0; i < ints.length; i++) {
33             str.append(ints[i]);
34             if (i == current) {
35                 str.append("@");
36             } else if (i == compare) {
37                 str.append("*");
38             }
39             if (i != ints.length - 1) {
40                 str.append(", ");
41             }
42         }
43         System.out.println(str.append("]"));
44     }
45 }

 

*printArray是为了显示处理流程,打印了当前数组中正在处理的元素(后面加了@)和涉及到要交换元素(后面加了*),后续两个算法演示此方法已省略,请注意。

<->表示出现了要交换元素的操作。

运行后输出以下内容,关键步骤见其中的注释。

*original[3, 5, 4, 1, 2, 6]  参与排序的数据
loop 0[3@, 5, 4, 1, 2, 6]    第一轮循环,先从左侧第一个元素3开始处理
	[3@, 5*, 4, 1, 2, 6] 和其余元素依次比较,由于从小到大排序,而3小于当前的5,所以不处理
	[3@, 5, 4*, 1, 2, 6] 只比较,不交换
	[3@, 5, 4, 1*, 2, 6] 只比较,不交换
	3<->1[1@, 5, 4, 3*, 2, 6] 发现3大于1 所以要交换两者位置
	[1@, 5, 4, 3, 2*, 6]
	[1@, 5, 4, 3, 2, 6*] 第一轮结束后,左侧第一个元素必定为最小值
loop 1[1, 5@, 4, 3, 2, 6]    第二轮循环,开始处理左侧第二个元素5
	[1, 5@, 4*, 3, 2, 6]
	5<->4[1, 4@, 5*, 3, 2, 6] 发现5大于4,交换两者位置
	[1, 4@, 5, 3*, 2, 6]
	4<->3[1, 3@, 5, 4*, 2, 6] 交换
	[1, 3@, 5, 4, 2*, 6]
	3<->2[1, 2@, 5, 4, 3*, 6] 交换
	[1, 2@, 5, 4, 3, 6*] 第二轮结束后,左侧第二个元素必定为剩余元素中的最小值,也就是每轮循环后都能拿到最小值
loop 2[1, 2, 5@, 4, 3, 6] 第三轮
	[1, 2, 5@, 4*, 3, 6]
	5<->4[1, 2, 4@, 5*, 3, 6] 交换
	[1, 2, 4@, 5, 3*, 6]
	4<->3[1, 2, 3@, 5, 4*, 6] 交换
	[1, 2, 3@, 5, 4, 6*]
loop 3[1, 2, 3, 5@, 4, 6] 第四轮
	[1, 2, 3, 5@, 4*, 6]
	5<->4[1, 2, 3, 4@, 5*, 6] 交换
	[1, 2, 3, 4@, 5, 6*]
loop 4[1, 2, 3, 4, 5@, 6] 第五轮
	[1, 2, 3, 4, 5@, 6*]
loop 5[1, 2, 3, 4, 5, 6@] 第六轮
*sorted[1, 2, 3, 4, 5, 6] 最终完成,打印出排序结果

 

 二 插入排序

1 把数据分成已排序和未排序的两部分,先从一侧开始,把第一个元素a当成已排好序的第一个元素,剩余元素为未排序部分
2 取第二个元素b和第一个元素a比较,如果比a小,则放置在a的左侧,大于或者等于a,放置在a的右侧, 也就是要确定新元素在排好序的元素里的位置,会涉及到已排好序的元素移动
比如左侧已排好的1 3 5,新来一个元素2,通过比较得出位置应该在1和3之间,这时就需要把3、5向后移动,留出2的位置,最终排成1 2 3 5
插入排序其实是分成了两步:第一步就是找到新元素在已排好序元素里的位置,第二步移动原有元素,给新元素腾出位置写入(核心是移动已排好序的元素,即腾位置)。

代码及输出说明如下

 1 import java.util.Arrays;
 2 
 3 public class InsertionSort {
 4     public static void main(String[] args) {
 5         int[] numbers = {3, 5, 4, 1, 2, 6};
 6         System.out.println("*original" + Arrays.toString(numbers));
 7         //插入排序:分成已排序和待排序两部分,再分两步进行 1找位置 2移动
 8         for (int i = 0; i < numbers.length; i++) {
 9             System.out.print("loop " + i);
10             printArray(numbers, i, i);
11             //找位置 即按大小找到所属的位置
12             int pos = 0;
13             for (int j = 0; j < i; j++) {
14                 if (numbers[i] >= numbers[j]) {
15                     pos++;
16                 }
17             }
18             //移动位置是当前数据位置时无需移动
19             if (pos == i) {
20                 continue;
21             }
22             //需要临时保存要移动的元素
23             int moveNum = numbers[i];
24             //从当前的元素开始向前移动数据 一直到要移动的位置
25             //如 1 2 3 4 5这些数,想把4移动到1和2之间,首先把3移动到4的位置,再把2移动到3的位置,最后把位置1即一开始2所处的位置直接替换成4
26             for (int j = i; j > pos; j--) {
27                 System.out.print("\t" + numbers[j] + "->" + numbers[j - 1]);
28                 numbers[j] = numbers[j - 1];
29                 System.out.println(Arrays.toString(numbers));
30             }
31             //移动到对应位置的元素
32             int oldNum = numbers[pos];
33             numbers[pos] = moveNum;
34             System.out.println("\t" + "set " + oldNum + " to " + moveNum + Arrays.toString(numbers));
35         }
36         System.out.println("*sorted" + Arrays.toString(numbers));
37     }

 

*original[3, 5, 4, 1, 2, 6]
loop 0[3@, 5, 4, 1, 2, 6] 第一轮开始时左侧第一个元素3,可以认为是排好序的部分,不过只有一个元素3而已
loop 1[3, 5@, 4, 1, 2, 6] 第二轮从左侧第二个元素5开始,和第一轮中排好序的部分(目前只有3)来比较,发现5大于3,从而确定5的位置为1,正好是当前元素所处的位置,所以不用处理,直接进入下一轮
loop 2[3, 5, 4@, 1, 2, 6] 第三轮从4开始
	4->5[3, 5, 5, 1, 2, 6] 这时排好序的部分元素为3 5,因此需要在3 5之间找到4所属的位置,经过循环查找(代码的13-17行),确定所属位置为1,即在3 5之间,开始把5向右移动一位
	set 5 to 4[3, 4, 5, 1, 2, 6] 再把原来5的位置替换成4即可
loop 3[3, 4, 5, 1@, 2, 6] 从1开始,此时排好序的部分元素为3 4 5,再把1放到这部分里,1替换成5,5替换成4,4替换成3)
	1->5[3, 4, 5, 5, 2, 6] 移动数据
	5->4[3, 4, 4, 5, 2, 6] 移动数据
	4->3[3, 3, 4, 5, 2, 6] 移动数据
	set 3 to 1[1, 3, 4, 5, 2, 6] 把1写入所属位置
loop 4[1, 3, 4, 5, 2@, 6] 从2开始
	2->5[1, 3, 4, 5, 5, 6] 移动数据
	5->4[1, 3, 4, 4, 5, 6] 移动数据
	4->3[1, 3, 3, 4, 5, 6] 移动数据
	set 3 to 2[1, 2, 3, 4, 5, 6] 把3写入所属位置
loop 5[1, 2, 3, 4, 5, 6@]
*sorted[1, 2, 3, 4, 5, 6]

 

三 选择排序

类似于冒泡排序,同样把数据分成了已排序和未排序两部分
1 同样从一侧数据开始比较,取第一个元素a, 在剩余的元素中找到小于a的最小的元素b, 然后a和b位置互换,保证了第一个位置的元素就是最小的元素。
2 然后从第二个元素c开始,从剩余元素中到最小的,和c位置互换,直到全部元素处理完成。
核心是从所有元素中选取出最小的值,然后交换

代码及输出说明如下

 1 import java.util.Arrays;
 2 
 3 public class SelectionSort {
 4     public static void main(String[] args) {
 5         int[] numbers = {3, 5, 4, 1, 2, 6};
 6         System.out.println("*original" + Arrays.toString(numbers));
 7         //选择排序:分成已排序和待排序两部分,每次从剩余元素中选取最小的和当前位置交换
 8         for (int i = 0; i < numbers.length; i++) {
 9             System.out.print("loop " + i);
10             printArray(numbers, i, i);
11             int min = numbers[i];
12             int pos = i;
13             for (int j = i + 1; j < numbers.length; j++) {
14                 if (numbers[j] < min) {
15                     min = numbers[j];
16                     pos = j;
17                 }
18             }
19             //相同的位置无需移动
20             if (pos == i) {
21                 continue;
22             }
23             System.out.print("\t" + min + "<->" + numbers[i]);
24             numbers[pos] = numbers[i];
25             numbers[i] = min;
26             System.out.println(Arrays.toString(numbers));
27         }
28         System.out.println("*sorted" + Arrays.toString(numbers));
29     }

 

*original[3, 5, 4, 1, 2, 6]
loop 0[3@, 5, 4, 1, 2, 6] 第一轮从3开始,当做已排好序的部分,剩余为未排序部分
	1<->3[1, 5, 4, 3, 2, 6] 从剩余的5 4 1 2 6中找到最小的1以及所属位置,并置换
loop 1[1, 5@, 4, 3, 2, 6]
	2<->5[1, 2, 4, 3, 5, 6] 从4 3 2 6中找到最小的2并和5置换
loop 2[1, 2, 4@, 3, 5, 6]
	3<->4[1, 2, 3, 4, 5, 6] 从3 5 6中找到最小的3并和4置换
loop 3[1, 2, 3, 4@, 5, 6]
loop 4[1, 2, 3, 4, 5@, 6]
loop 5[1, 2, 3, 4, 5, 6@]
*sorted[1, 2, 3, 4, 5, 6]

 

总结下这三种排序,做为最基本的排序算法其核心都是元素的相互比较和移动,不涉及新的内存空间申请。后续会再学习下归并和快速排序,通过引入新的内存空间来加速排序(空间换时间)。

 

扩展阅读

B站的排序视频 直观体验下不同排序的处理过程 https://www.bilibili.com/video/BV1DP4y1n71W?share_source=copy_web
百度百科相关算法介绍
冒泡排序(可以理解下为啥叫冒泡) https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/4602306
插入排序 https://baike.baidu.com/item/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F
选择排序 https://baike.baidu.com/item/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F

posted @ 2022-06-15 15:14  binary220615  阅读(52)  评论(0编辑  收藏  举报