【入门篇|第二篇】从零实现选择、冒泡、插入排序(含对数器)
无前置所需知识,建议会的直接跳过(对数器不建议跳)
文章目录
- 前言
- 1 基础排序算法
- 1.1 选择排序
- 1.2 冒泡排序
- 1.3 插入排序
- 1.4 三种算法对比
- 2. 对数器
- 2.1 使用场景
- 2.2 对数期概述
- 2.3 对数期实现
前言
排序是将一组数据按照特定顺序(升序或降序)重新排列的过程。本文介绍三种最基础的排序算法,它们是理解更复杂排序算法的基础。
1 基础排序算法
公共工具方法:
//交换数组中两个元素的位置
public static void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;
1.1 选择排序
在未排序序列中找到最小(或最大)元素,放到排序序列的起始位置,然后继续从剩余未排序元素中寻找最小元素,放到已排序序列的末尾。
核心思想:在
i ~ n-1
的范围上,找到最小值并放在位置 i,然后在i+1 ~ n-1
范围上继续此操作。
算法步骤演示
以数组 [3, 2, 4, 1]
为例:
初始状态: [3, 2, 4, 1]
第1轮: 在0~3范围找最小值1,与位置0交换 → [1, 2, 4, 3]
第2轮: 在1~3范围找最小值2,与位置1交换 → [1, 2, 4, 3] (无需交换)
第3轮: 在2~3范围找最小值3,与位置2交换 → [1, 2, 3, 4]
结果: [1, 2, 3, 4]
代码示例:
// 选择排序public static void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int minIndex, i = 0; i < arr.length - 1; i++) {// minIndex i~n-1 范围最小值minIndex = i;for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}swap(arr, i, minIndex);}}
1.2 冒泡排序
通过重复遍历要排序的数列,一次比较两个相邻元素,如果它们的顺序错误就交换过来。重复进行直到没有需要交换的元素。
核心思想:在
0 ~ end
范围上,相邻位置较大的数"冒泡"到右边,最大值最终到达位置 end,然后在0 ~ end-1
范围上继续。
算法步骤演示:
以数组 [3, 4, 1, 5, 2]
为例:
初始状态: [3, 4, 1, 5, 2]
第1轮: 比较相邻元素并交换3,4(不交换) → [3, 4, 1, 5, 2]4,1(交换) → [3, 1, 4, 5, 2]4,5(不交换) → [3, 1, 4, 5, 2]5,2(交换) → [3, 1, 4, 2, 5] (最大值5冒泡到末尾)第2轮: 在前4个数中冒泡 → [1, 3, 2, 4, 5]
第3轮: 在前3个数中冒泡 → [1, 2, 3, 4, 5]
第4轮: 在前2个数中冒泡 → [1, 2, 3, 4, 5] (无需交换)
结果: [1, 2, 3, 4, 5]
代码示例:
// 冒泡排序public static void bubbleSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int end = arr.length - 1; end > 0; end--) {for (int i = 0; i < end; i++) {if (arr[i] > arr[i + 1]) {swap(arr, i, i + 1);}}}}
1.3 插入排序
将数组分为已排序和未排序两部分,逐个将未排序部分的元素插入到已排序部分的正确位置。
核心思想:保证
0 ~ i
范围已经有序,新来的数从右到左滑动到合适位置插入。
以数组 [1, 4, 5, 2]
为例:
初始状态: [1, 4, 5, 2] (位置0的1看作已排序)
第1轮: 将4插入已排序部分[1] → [1, 4, 5, 2] (4>1,位置正确)
第2轮: 将5插入已排序部分[1,4] → [1, 4, 5, 2] (5>4,位置正确)
第3轮: 将2插入已排序部分[1,4,5]2<5,交换 → [1, 4, 2, 5]2<4,交换 → [1, 2, 4, 5]2>1,停止
结果: [1, 2, 4, 5]
注:该程序直到左边的数<=当前的数
或左边没有数字
停止
public static void insertionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 1; i < arr.length; i++) {for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {swap(arr, j, j + 1);}}}
1.4 三种算法对比
排序算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 适用场景 |
---|---|---|---|---|---|---|
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 | 数据量小,内存限制严格 |
冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 | 几乎有序的数据,教学演示 |
插入排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 | 小数据集,几乎有序的数据 |
性能特点:
- 选择排序:交换次数最少(n-1次),但比较次数固定(n²/2次)
- 冒泡排序:可以提前结束,最好情况下只需一轮,适合检测数组是否已排序
- 插入排序:在实际应用中通常比选择和冒泡排序更快,对于小数据集性能优异
稳定性说明:
- 稳定排序:相等元素的相对顺序在排序后保持不变
- 不稳定排序:相等元素的相对顺序可能改变
2. 对数器
2.1 使用场景
1.你在网上找到了某个公司的面试题,想了好久,感觉自己会做,但找不到在线测试
2.你和朋友交流面试题,想了好久,感觉自己会做,但是找不到在线测试
3.你在网上做笔试,前几个测试用例都过了,突然巨大无比的数据量来了,代码报错了,且如此大的数据根本看不出哪错了。
2.2 对数期概述
什么是对数器?
有一个算法题,你想出暴力解与最优解,然后你随机生成数据,控制数据的长度和范围,跑很多次,如果暴力解和最优解的答案基本一样,则正确,如果两者答案不一样,可以把原始测试数据打印,将数组长度小一点,在去找问题即可,这就是对数器。
2.3 对数期实现
1.你想要测的方法a(最优解)
2.实现复杂度不好但容易实现的方法b(暴力解)
3.实现一个随机样本产生器(长度、值随机)
4.把方法a和方法b跑相同的输入样本,看看得到的结果是否一样
5.如果有一个随机样本比对结果不一致,打印出错样本,进行人工干预,改正方法a和b
6.样本数量很多组仍正确,可确定最优解a正确
当处于第5步时,找到一个数据量小的错误样本,去带入debug,把错误例子带入代码排查
Print打法、断点技术皆可
示例:随机数组
public static int[] randomArray(int n, int v) {int[] arr = new int[n];for (int i = 0; i < n; i++) {// Math.random() -> double -> [0,1)范围山的一个小数,0.37463473126、0.001231231,等概率!// Math.random() * v -> double -> [0,v)一个小数,依然等概率// (int)(Math.random() * v) -> int -> 0 1 2 3 ... v-1,等概率的!// (int) (Math.random() * v) + 1 -> int -> 1 2 3 .... v,等概率的!arr[i] = (int) (Math.random() * v) + 1;}return arr;}
示例:复制数组
public static int[] copyArray(int[] arr) {int n = arr.length;int[] ans = new int[n];for (int i = 0; i < n; i++) {ans[i] = arr[i];}return ans;}
示例:主程序
// 随机数组最大长度int N = 200;// 随机数组每个值,在1~V之间等概率随机int V = 1000;// testTimes : 测试次数int testTimes = 50000;System.out.println("测试开始");for (int i = 0; i < testTimes; i++) {// 随机得到一个长度,长度在[0~N-1]int n = (int) (Math.random() * N);// 得到随机数组int[] arr = randomArray(n, V);int[] arr1 = copyArray(arr);int[] arr2 = copyArray(arr);int[] arr3 = copyArray(arr);selectionSort(arr1);bubbleSort(arr2);insertionSort(arr3);if (!sameArray(arr1, arr2) || !sameArray(arr1, arr3)) {System.out.println("出错了!");// 当有错了// 打印是什么例子,出错的// 打印三个功能,各自排序成了什么样// 可能要把例子带入,每个方法,去debug!}}System.out.println("测试结束");}
细节:使用三个复制的数组去运行算法,当出现问题时,可以直接打印原数组,检测问题
本文为算法学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。