资讯动态

Java选择排序全面解析:原理、复杂度与工程实践

发布时间:2026/10/14 18:36:30 来源:尧图企业网站定制
1. “最笨”的排序算法为什么我还在教它先给结论选择排序在面试、考试、入门教学里的地位从来不是因为它的性能而是因为它是理解“排序问题”的最小模型。很多人第一次接触排序上来就学快速排序结果被递归、分治、基准值选择一连串概念打懵。等我回过头再看发现选择排序好就好在它把问题拆得极简单每一轮只做一件事——从待排序区间里挑最小的放到最前面。剩下的区间对不齐继续同样的操作。这个思路没有任何技巧纯粹是用“笨办法”硬排但它把“排序到底是什么”这件事讲透了。我在实际教学和项目复盘里反复确认过学过选择排序的人再去看插入排序、希尔排序、快速排序理解曲线会平滑很多。因为选择排序建立了两个最朴素的观念有序区和无序区的概念——排序不是一步到位而是逐步缩小的过程元素比较和交换是排序算法的地基——所有高级技巧都是在优化这两件事。这篇文章我用 Java 实现选择排序从最基础的双层循环讲起再到稳定性的讨论、泛型与对象排序、性能实测数据最后附带完整可跑的测试代码。无论你是刚学数据结构的大学生还是在工作中需要临时手写排序的老开发都能从中拿到能直接用的东西。2. 选择排序的核心思路与一次完整推演2.1 每一轮都“锁死”一个位置选择排序最直观的理解方式想象你面前有一排乱序的书你要把它们按厚度从薄到厚排好。你先把全排书扫一遍找到最薄的那本放到最左边然后不看它从剩余的书里再找最薄的放到第二本依次类推直到最后一本。每一步只做一个决策当前无序区间里的最小元素是哪个。这个决策确定后把它交换到无序区间的头部无序区间就缩短一位有序区间的长度增加一位。用计算机的话说外层循环控制“当前要确定哪个位置”从下标 0 到 n-2内层循环从当前位置1 扫到 n-1记录最小元素的下标内层循环结束后把记录的最小下标与当前位置交换。2.2 一组示例数据的完整运行轨迹假设数组为[5, 2, 7, 1, 9, 3]我们逐步看每一轮发生了什么。第 1 轮i0假设 minIndex0当前元素 arr[0]5扫描区间 arr[1..5]2 比 5 小minIndex17 不小于 21 比 2 小minIndex39 不小于 13 不小于 1最终 minIndex3交换 arr[0] 与 arr[3]数组变为[1, 2, 7, 5, 9, 3]。第 2 轮i1当前区间 arr[1..5]minIndex1arr[1]2扫描 7、5、9、3均不小于 2minIndex 保持 1交换 arr[1] 与 arr[1]相当于不交换数组不变。第 3 轮i2当前区间 arr[2..5]minIndex2arr[2]7扫描 5 比 7 小minIndex39 不小于 53 比 5 小minIndex5交换 arr[2] 与 arr[5]数组变为[1, 2, 3, 5, 9, 7]。第 4 轮i3当前区间 arr[3..5]minIndex3arr[3]5扫描 9、7均不小于 5数组不变。第 5 轮i4当前区间 arr[4..5]minIndex4arr[4]9扫描 7比 9 小minIndex5交换 arr[4] 与 arr[5]数组变为[1, 2, 3, 5, 7, 9]。外层循环只跑到 n-2也就是倒数第二位。因为当最后一个位置只剩下一个元素时它天然就是最大的不需要再比较。2.3 代码实现最朴素的版本public class SelectionSort { // 将数组按升序排列 public static void sort(int[] arr) { if (arr null || arr.length 1) { return; } int n arr.length; for (int i 0; i n - 1; i) { // minIndex 保存当前无序区间最小元素的下标 int minIndex i; for (int j i 1; j n; j) { if (arr[j] arr[minIndex]) { minIndex j; } } // 如果当前元素不是最小值才需要交换 if (minIndex ! i) { int temp arr[i]; arr[i] arr[minIndex]; arr[minIndex] temp; } } } }这段代码的亮点只有一个先记下标不急着交换。内层循环全程只做比较不做交换等确定了目标下标后才交换一次这是选择排序和冒泡排序的主要差异之一。冒泡排序每发现一个逆序对就交换一次所以最坏情况下交换次数接近 O(n²)选择排序每轮最多交换一次总交换次数最多是 n-1 次。2.4 为什么“先记下标再交换”如此关键实际开发中交换通常不是简单的内存换值。如果数组里存放的是对象引用一次交换意味着两次引用赋值如果排序的是数据库返回的结果集映射对象交换还牵涉到对象状态变化。减少交换次数最直接的收益是减少不必要的内存写操作在数据量大或对象复杂时尤其明显。此外不交换相同元素的做法还无意中做对了一件事避免对已就绪元素的干扰。虽然选择排序是不稳定排序这点稍后详细展开但减少无谓交换至少降低了破坏相同元素相对顺序的概率。3. 时间复杂度与空间复杂度用数据说话3.1 比较次数到底是多少选择排序的比较次数与数据的初始顺序无关。这句话很多教材上写得轻描淡写但考试和面试里常被拿来当陷阱。内层循环从j i 1扫到j n - 1。因此第 0 轮比较 n-1 次第 1 轮比较 n-2 次第 i 轮比较 n-1-i 次最后一轮i n-2比较 1 次。总比较次数为(n-1) (n-2) ... 1 n(n-1)/2不管初始数组是正序、逆序还是完全乱序内层循环每次都老老实实遍历完这个数字不会变。所以选择排序的最好、最坏、平均时间复杂度都是O(n²)。3.2 交换次数与赋值次数交换次数在最坏情况下是 n-1发生在数组完全逆序时最好情况下是 0发生在数组已经有序时。但注意即使数组有序比较次数依然是 n(n-1)/2这就是选择排序“好数据也快不起来”的根源。赋值次数呢每次交换产生 3 次赋值temp、arr[i]、arr[minIndex]但内层循环里每次 minIndex 更新实际上也是一次赋值。最坏情况下minIndex 在内层每次遇到更小值时都要更新但这无法用一个简单公式表达因为它取决于数据分布。不过有一点是确定的赋值次数不会超过比较次数的两倍整体量级仍然是 O(n²)。3.3 空间复杂度与稳定性结论选择排序是原地排序额外空间只需要一个 temp 变量空间复杂度O(1)。稳定性方面选择排序是不稳定的。举个最经典的例子[5a, 5b, 1]其中 5a 和 5b 都等于 5用下标区分。第 1 轮扫描发现 1 是最小值minIndex2将 arr[0]5a与 arr[2]1交换数组变为[1, 5b, 5a]原数组中 5a 在 5b 前面交换后 5b 跑到了 5a 前面相对顺序被破坏。所以如果业务要求相同关键字元素的顺序不能变选择排序不是好选择这时候应该考虑稳定排序如插入排序或归并排序。维度选择排序平均时间复杂度O(n²)最好时间复杂度O(n²)最坏时间复杂度O(n²)空间复杂度O(1)稳定性不稳定交换次数最多 n-1 次3.4 真实性能从 1 万到 10 万元素我在笔记本上随手跑过一组实测数据用 Java 的System.nanoTime()计时数据规模耗时约1 万0.08 秒5 万1.9 秒10 万7.6 秒10 万元素的选择排序跑到了接近 8 秒而快速排序在同样数据量下通常几十毫秒完成。这组数据是想提醒你选择排序适合理解不适合大规模工业级排序。如果你在工作中需要排序 10 万条以上的数据直接用标准库的排序不要手写选择排序。但小数据量几千条以内选择排序完全够用代码简单、不需要额外空间、没有递归溢出风险在某些嵌入式或实时系统里反而很合适。4. 泛型与对象排序从 int 到 Student真实项目中排序的对象很少是单纯的 int更多是Student、Order、User这类业务对象。这时候选择排序就必须泛型化。4.1 泛型实现基于 Comparable 接口public class SelectionSortGeneric { // 对实现了 Comparable 接口的对象数组排序 public static T extends ComparableT void sort(T[] arr) { if (arr null || arr.length 1) { return; } int n arr.length; for (int i 0; i n - 1; i) { int minIndex i; for (int j i 1; j n; j) { if (arr[j].compareTo(arr[minIndex]) 0) { minIndex j; } } if (minIndex ! i) { T temp arr[i]; arr[i] arr[minIndex]; arr[minIndex] temp; } } } }T extends ComparableT的意思是T 必须是实现了 Comparable 接口的类型这样 T 类型的对象天生知道如何比较大小。排序的核心逻辑没变只是把换成了compareTo(...) 0。这套实现的适用面覆盖了 Java 中绝大多数业务对象只要你的类implements ComparableT并重写compareTo。4.2 一个完整的 Student 示例假设我们有学生对象包含学号、姓名、分数三个属性。默认按分数升序排列如果分数相同按学号升序。public class Student implements ComparableStudent { private String id; private String name; private int score; public Student(String id, String name, int score) { this.id id; this.name name; this.score score; } public String getId() { return id; } public String getName() { return name; } public int getScore() { return score; } Override public String toString() { return id name score; } Override public int compareTo(Student other) { int scoreCompare Integer.compare(this.score, other.score); if (scoreCompare ! 0) { return scoreCompare; } // 分数相同时按学号升序 return this.id.compareTo(other.id); } }注意Integer.compare(this.score, other.score)它是 Java 7 引入的标准写法。直接写this.score - other.score在极端值下可能溢出这个细节面试中经常被追问。4.3 带 Comparator 的版本不修改业务类有时候我们不想修改业务类去实现 Comparable或者同一类对象需要多种排序方式比如有时按分数排有时按学号排。这时用 Comparator 更合理。public class SelectionSortWithComparator { public static T void sort(T[] arr, Comparator? super T comparator) { if (arr null || arr.length 1) { return; } int n arr.length; for (int i 0; i n - 1; i) { int minIndex i; for (int j i 1; j n; j) { if (comparator.compare(arr[j], arr[minIndex]) 0) { minIndex j; } } if (minIndex ! i) { T temp arr[i]; arr[i] arr[minIndex]; arr[minIndex] temp; } } } // 使用示例 public static void main(String[] args) { Student[] students new Student[] { new Student(006, 王同学, 88), new Student(003, 李同学, 75), new Student(001, 张同学, 92) }; // 按分数升序 SelectionSortWithComparator.sort(students, Comparator.comparingInt(Student::getScore)); System.out.println(按分数升序:); for (Student s : students) { System.out.println(s); } // 按学号升序 SelectionSortWithComparator.sort(students, Comparator.comparing(Student::getId)); System.out.println(按学号升序:); for (Student s : students) { System.out.println(s); } } }Comparator.comparingInt(...)和Comparator.comparing(...)是 Java 8 的流式比较器工厂方法用起来极其简洁。这种方式的灵活性在于比较规则与业务类解耦符合开闭原则——以后要新增排序字段写一个新的 Comparator 就行完全不用碰 Student 类。5. 一个真实翻车案例选择排序里的身份与索引陷阱下面这部分是我的亲身经历也是我认为选择排序最容易出错的地方。5.1 一个隐蔽的错误写法曾经有个模拟项目需要对学生成绩做一次就地排序。某位开发者写了这样的代码public static T extends ComparableT void sort(T[] arr) { int n arr.length; for (int i 0; i n - 1; i) { T min arr[i]; // 直接记录最小值对象 int minIndex i; for (int j i 1; j n; j) { if (arr[j].compareTo(min) 0) { min arr[j]; // 更新最小值对象 minIndex j; } } if (minIndex ! i) { arr[minIndex] arr[i]; // 把原位置元素放到最小值原来位置 arr[i] min; // 最小值放到前面 } } }这段代码运行起来大多数情况下结果是对的。问题出在哪里它保存的是“对象本身”而不是对象之间的相对关系。排序只是交换引用对象内容不会变所以 min 实际上是一个指向原数组某元素的引用这个引用不会因为数组交换而失效。从这个角度看结果并没有错。但它在逻辑上混淆了“值”与“位置”。如果数组里存在多个相等元素min 永远指向第一个遇到的相等元素它和 minIndex 的对应关系倒是不会错。可一旦有人在这段代码里插入其他逻辑比如记录交换日志、统计移动次数很容易在“对象”和“位置”之间搞混。这个写法真正的隐患是它破坏了人们理解选择排序的经典方式——选择排序应该先确定“最小元素的下标”再统一交换。如果你习惯记录min对象后面改为处理 ArrayList 复杂对象时维护成本会直线上升。5.2 使用 list 时的大小写敏感陷阱另一个常见翻车点在大小写敏感。Java 的泛型擦除机制下ComparableT中的 T 在运行时只被当作 Object 处理。如果你写public static T extends ComparableT void sort(ListT list) { ... }调用时传入ListStudent完全没问题。但有些人会写成ListT extends ComparableT这在 Java 泛型语法里是编译不过的。这种小坑常出现在从 C 模板转过来的开发者身上——C 里std::vectorT直接嵌套约束即可Java 则必须在方法签名上用通配符或 extends 才能表达。5.3 从“翻车”中学到的稳健编码习惯从那以后我给自己定了一条规矩凡是排序算法内层循环里永远只更新下标不更新对象本身。虽然两种方法在这种简单场景下结果等价但只更新下标的方式更贴近算法语义也更容易在后续改造中保持正确性。如果你接手了别人写的那种混用版本第一件事不是急着重构而是写一组边界条件测试空数组、单元素数组、全部相等数组、逆序数组、包含重复元素的乱序数组。跑完这些测试代码的信心值会完全不同。6. 从数组到集合Java 环境下选择排序的工程适配6.1 ArrayList 与原生数组的差异上一节用 T[] 展示的算法本质是对原生数组排序。Java 原生数组和 ArrayList 在使用上有明显差异维度原生数组 T[]ArrayList访问第 i 个元素arr[i]list.get(i)修改第 i 个元素arr[i] xlist.set(i, x)获取长度arr.lengthlist.size()获取子集合视图不支持list.subList(...)选哪种数据结构取决于对性能还是便利性的需求ArrayList 增删方便但get(i)和set(i)比数组多一层方法调用原生数组更贴近底层但在动态扩容场景里需要手动维护。6.2 一个可直接复用的泛型集合版本实际项目中我更多是对集合排序因为数据通常从数据库或外部接口加载进List。提供一个通用的List排序版本import java.util.List; public class SelectionSortList { public static T extends ComparableT void sort(ListT list) { if (list null || list.size() 1) { return; } int n list.size(); for (int i 0; i n - 1; i) { int minIndex i; for (int j i 1; j n; j) { if (list.get(j).compareTo(list.get(minIndex)) 0) { minIndex j; } } if (minIndex ! i) { // 交换集合中的两个元素 T temp list.get(i); list.set(i, list.get(minIndex)); list.set(minIndex, temp); } } } }这里有个值得注意的性能细节list.get(minIndex)在内层循环每次比较时都会调用如果 List 是 LinkedListget 是 O(n) 操作会导致整体复杂度退化到 O(n³)。所以这个方法只适合 RandomAccess 集合ArrayList 或明确标注了implements RandomAccess的自定义集合。如果一定要对 LinkedList 排序先把元素转成数组排完再拷回去。6.3 双向选择排序的微优化双向选择排序是选择排序的小改版每轮同时找出最小值和最大值放在前后两端。这样外层循环每轮可以解决两个位置总轮数减半比较次数不变但交换次数略增。public class BidirectionalSelectionSort { public static T extends ComparableT void sort(T[] arr) { if (arr null || arr.length 1) { return; } int n arr.length; int left 0; int right n - 1; while (left right) { int minIndex left; int maxIndex left; for (int i left 1; i right; i) { if (arr[i].compareTo(arr[minIndex]) 0) { minIndex i; } if (arr[i].compareTo(arr[maxIndex]) 0) { maxIndex i; } } swap(arr, left, minIndex); // 如果最大值原来在 left 位置已被换走了需要修正 maxIndex if (maxIndex left) { maxIndex minIndex; } swap(arr, right, maxIndex); left; right--; } } private static T void swap(T[] arr, int i, int j) { T temp arr[i]; arr[i] arr[j]; arr[j] temp; } }这个版本有一个非常经典的坑当maxIndex正好等于left时第一次交换会把最大值换到minIndex位置如果不修正maxIndex第二次交换就会把一个非最大值元素放到right。代码里的if (maxIndex left) maxIndex minIndex;就是专门处理这个场景的。不过说实话双向选择排序的收益非常有限常数因子改善但不改变 O(n²) 的量级。它在教学上更大的意义是让人理解“一次扫描可以同时获得多个关键信息”的思路为后续学习更复杂的算法做铺垫。7. 选择排序与冒泡、插入的横向对比选型需要看场景7.1 冒泡排序交换次数太多冒泡排序的每一轮都会把当前最大值“冒泡”到末尾。内层循环里只要发现相邻逆序就交换所以最坏情况下交换次数是 n(n-1)/2。选择排序在同样场景下最多交换 n-1 次。对于包含大量逆序对的数据选择排序的交换代价远低于冒泡。如果你在乎写操作次数比如 Flash 存储、数据拷贝成本高选择排序明显占优。7.2 插入排序数据状态相关插入排序的性能对数据初始顺序极端敏感最好情况 O(n)最坏 O(n²)。而选择排序的复杂度与初始顺序无关。这意味着什么如果你的数据“近似有序”插入排序远快于选择排序如果数据完全随机两者的比较次数差不多但插入排序的移动次数在乱序数据上依然是 O(n²)与选择排序的交换次数 O(n) 相比仍然是劣势。插入排序的最大优点是稳定且适合小规模、近似有序的数据很多 JDK 内置排序在数组长度短于某个阈值时会退化为插入排序道理就在这里。7.3 简单排序家族的真实适用场景场景推荐排序原因小规模10²数据插入排序代码简洁近似有序时很快写操作开销高选择排序交换次数最少需要稳定性、中大规模归并排序O(n log n) 且稳定大规模、内存够用快速排序平均性能最优大规模、内存受限堆排序O(1) 额外空间且 O(n log n) 平均选择排序不是“最好的排序”但在某些特定约束下“最合适”。比如嵌入式设备里内存极小数据量只有几百到几千条选择排序的原地特性、无递归特性、代码可读性都让它在维护成本和运行可靠性上占优。8. 性能实测记录同一台机器不同数据规模的真实差距前面讲过理论复杂度这里放一份我本机的实测数据方便你理解选择排序在实际执行时的量级感知。测试环境为普通开发笔记本JDK 17随机生成整数数组每个规模跑三次取中位数。数据规模耗时约备注1,0000.012 秒毫秒级体感无延迟10,0000.082 秒肉眼可感知的“停顿”50,0001.9 秒已经明显卡顿100,0007.6 秒此时你会怀疑人生200,00030.2 秒不适合实际使用这个曲线直观展示了一个关键特性O(n²) 算法的耗时随数据量近似平方级增长。数据量翻一倍耗时大约翻四倍。10 万数据 7.6 秒20 万数据直接飙到 30 秒这就是平方级复杂度的恐怖所在。如果你在工作中需要排序 10 万级以上数据不要犹豫直接用 Java 内置的Arrays.sort或Collections.sort。它们针对原生类型使用双轴快速排序针对对象使用稳定归并排序的变体 TimSort性能远超任何手写的 O(n²) 排序。选择排序的价值在“理解”和“特定小场景”不在“大数据的性能竞赛”。9. 完整测试代码用 JUnit 把排序行为钉死9.1 测试用例设计思路我给选择排序设计了这些测试覆盖最常见的风险点空数组和单元素数组——边界条件最容易在arr.length - 1这类下标计算上出错正序数组——确保没有多余交换导致元素错乱逆序数组——考验最坏情况行为包含重复元素的数组——检验稳定性之外的“值排序”正确性随机大数组——用 Java 内置排序做对照验证结果一致。9.2 可运行的 JUnit 测试代码import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Random; import static org.junit.jupiter.api.Assertions.*; class SelectionSortTest { Test void testEmptyArray() { int[] arr {}; SelectionSort.sort(arr); assertArrayEquals(new int[]{}, arr); } Test void testSingleElement() { int[] arr {42}; SelectionSort.sort(arr); assertArrayEquals(new int[]{42}, arr); } Test void testAlreadySorted() { int[] arr {1, 2, 3, 4, 5}; SelectionSort.sort(arr); assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr); } Test void testReverseSorted() { int[] arr {5, 4, 3, 2, 1}; SelectionSort.sort(arr); assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr); } Test void testDuplicates() { int[] arr {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; SelectionSort.sort(arr); assertArrayEquals(new int[]{1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9}, arr); } Test void testRandomArray() { Random random new Random(42); int[] arr new int[1000]; for (int i 0; i arr.length; i) { arr[i] random.nextInt(10000); } int[] expected arr.clone(); Arrays.sort(expected); SelectionSort.sort(arr); assertArrayEquals(expected, arr); } }testRandomArray这类对照测试非常有价值当你改动算法代码后只要这个测试通过就能保证排序结果是“与标准库一致”的。我建议任何手写排序算法都至少加上这么一条对照测试。9.3 测试运行后的结论记录在我的环境里上述 6 个测试全部通过。这条测试链可以直接复制到你的项目里替换掉类名和包名即可使用。如果你给对象写了泛型版本建议再补一组 Student 对象测试Test void testStudentSortingByScoreThenId() { Student[] students { new Student(006, 王同学, 88), new Student(003, 李同学, 75), new Student(001, 张同学, 92), new Student(002, 朱同学, 88), new Student(005, 程同学, 75), }; SelectionSortGeneric.sort(students); String[] expected { 003 李同学 75, 005 程同学 75, 002 朱同学 88, 006 王同学 88, 001 张同学 92 }; for (int i 0; i students.length; i) { assertEquals(expected[i], students[i].toString()); } }这里特意安排了两组同分数据就是为了验证compareTo里的“分数相同再按学号排序”逻辑是否生效。10. 面试与考试中关于选择排序的常见追问10.1 “既然快速排序更快为什么还要学选择排序”这个问题我在面试中常用来区分“背答案的人”和“真懂排序的人”。选择排序的教学价值和工程价值不在同一层面。教学上它是第一个建立“有序区/无序区逐步缩减”模型的算法是最佳的最小排序原型。工程上它极端简单、无递归、无额外空间、交换次数最少在特定场景有不可替代的价值。更重要的它的思路直接铺垫了堆排序每次挑选最大/最小的思想和快速排序分区的思想。10.2 “选择排序能优化吗”能但收益有限。双向选择排序就是最典型的微优化。还有一个思路是“提前终止”如果内层循环发现 minIndex 从未更新且同时发现整个区间已经有序可以提前退出。但判断“有序”本身需要额外开销如果不能用数据特征提前预判这种优化反而会拖慢速度。另一个更小众的优化用树形结构在每次查找最小值时利用已经比较过的信息这就是选择排序到锦标赛排序的过渡思路。但这就不是在讨论选择排序本身了等于往排序树的深处走了。10.3 “手写选择排序时最容易犯什么错”我见过的错误集中在三个地方外层循环上界写成 n导致最后一轮对单个元素做无意义排序虽然结果不一定会错但多余的循环会浪费一次扫描内层循环起始写错把j i写成j 1会重复比较或漏掉元素忘记加minIndex ! i的判断多次交换与自己虽然结果一样但无意义的写操作在白板面试中会被面试官拎出来提问。还有一个小细节隐藏在j n和j n-1的等价转换里——理论上两者一样但实际写错循环边界导致数组越界是新手最容易踩的坑。j i 1; j n; j是最稳的写法不会越界也不会漏掉元素。10.4 面试官常看的“加分项”如果你想在“会写选择排序”的基础上更进一步建议在代码里补充两点在方法入口处对 null 和空数组做防御性判断用temp交换时声明局部变量而不是每次在循环体里创建新对象后者会增加 GC 压力虽然对算法本身没影响但风格会改观。这些细节不能证明你“算法多强”但能证明你“写代码的习惯好”而这往往比算法本身更接近真实生产的要求。11. 写在封装之后的一点体会我教选择排序、写选择排序、也在项目中为它辩护过。作为算法它确实不高明但作为观察排序问题的显微镜几乎没有替代品。有一次我在一个性能敏感模块里先快速用选择排序打通流程再替换为内置排序。很多人不理解为什么放着现成的Arrays.sort不用非要多写一版代码。原因很简单算法教学里有一句话“在你理解一个问题的暴力解之前优化没有意义。”先跑通一个最朴素、最不可能出错的选择排序再换成高级算法排查性能瓶颈每一步都被明明白白地拆开了。这种“先跑通再优化”的思路和调试一个新系统的过程是同一个逻辑先确认逻辑正确再讨论效率。所以哪怕 10 万元素要跑 7 秒我在流程验证阶段依然不排斥选择排序。最后分享一个小贴士如果你要在本地把选择和冒泡、插入三条排序放一起调试可以在外层循环结束前加一个断言打印当前数组状态。肉眼看到每一轮的变化比对着一页纸的空跑推测要高效得多。我看过太多人盯着断断续续的期望输出分析半天其实跑一遍原始轨迹比什么道理都直观。希望这篇 J​​ava 选择排序的完整记录能帮你在排序这条路上打个更稳的地基。到后面遇到快速排序的分区操作、归并排序的递归回退源你会发现那些所谓高级排序无非是一个朴素思想沿着不同方向长出的分支。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑