尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

排序算法入门:冒泡排序原理、多语言实现与优化详解

排序算法入门:冒泡排序原理、多语言实现与优化详解 冒泡排序可以说是大多数人踏入数据结构与算法这道门之后见到的第一个像样的排序方法。哪怕后来你学会了快排、堆排、归并回头再看这个“老古董”它依然是理解算法思维最顺手的起点。我最早在学校里写冒泡排序的时候脑子里只有一句话“拿老大和老二比大的往后站”后来带团队面试新人也总爱拿它当开胃菜——因为从一段冒泡排序的写法真的能看出一个人对边界条件、循环终止、性能浪费的敏感度。这篇文章不打算把冒泡排序包装成多高大上的东西就老老实实拆开讲它为什么叫“冒泡”标准写法怎么落地C、C语言、Java、Python 各自怎么写最地道以及怎么优化、什么时候真正该用它。标题就是《排序算法-冒泡排序》热词里反复出现的也是这几个语言版本和优化点。不管你是在校学生准备考试、刚刷力扣的初学者还是想快速复习一遍排序细节的开发者这篇都值得你花十分钟看完。要注意网上不少版本在边界条件和提前退出上存在瑕疵直接抄到面试题里会翻车我后面会专门把那些隐藏很深的坑一一指出来。1. 冒泡排序的核心思路与设计拆解1.1 为什么叫“冒泡”这个名字其实特别形象。想象一缸水底部的气泡会不断向上浮动越接近水面气泡越大。冒泡排序的做法就是从左到右依次比较相邻的两个元素如果前一个比后一个大就把它们交换位置。这样一来每一轮比较下来当前范围内最大的那个数就会像气泡一样一路“冒”到数组的最右端。每一轮结束最右边的位置就被固定住了。下一轮排序就不需要再碰这个位置只需要处理它左侧的那段区间。这个“内层比较长度逐轮减1”的过程是整个冒泡排序的骨架。理解了这个机制你自然就明白为什么一轮内层循环是j len - 1 - i而不是j len - 1。我用一个活生生的例子演示一下。数组为[5, 1, 4, 2, 8]从小到大排序。第一轮比较5和1交换数组变为[1, 5, 4, 2, 8]比较5和4交换数组变为[1, 4, 5, 2, 8]比较5和2交换数组变为[1, 4, 2, 5, 8]比较5和8不交换数组保持[1, 4, 2, 5, 8]第一轮结束最大值8已经落在了最后一位。第二轮只需要处理前四个元素[1, 4, 2, 5]比较1和4不交换比较4和2交换数组变为[1, 2, 4, 5, 8]比较4和5不交换第二轮结束第二大值5落到了倒数第二位。这样反复下来数组最终有序。你会发现每一轮结束时右侧的元素就像一个已经码好的墙面一点点向左延伸直到整面墙砌完。1.2 用生活类比理解它的本质如果你觉得上面的数组演示还是有点抽象换个场景。体育课上老师让同学按身高从低到高排队冒泡排序的做法就是从队伍最左边开始让第一个同学和第二个同学比身高高的往后挪一步接着第二个同学和第三个同学比高的继续往后挪。就这样一路比下去最高的那个同学就被“顶”到了队伍末尾。然后老师宣布“最后那位同学你站好了别动。”接着老师又从头开始在剩下的同学里重复同样的操作把剩下人里最高的再顶到倒数第二个位置。这就是冒泡排序最本质的思维模型。它不做全局判断不搞花哨策略就靠一次次相邻比较和交换把一个局部有序逐步扩大成全局有序。这种“局部反复比较相邻交换”的过程恰好体现了排序算法里最基础的“交换排序”思想。理解了这一个类比代码里的两层循环就不难写了——外层循环管“谁已经站好”内层循环管“还剩多少人要比较”。1.3 冒泡排序的算法特性清单既然是一篇讲排序的博文一些专业术语必须交代清楚方便你后面和面试官聊的时候不掉链子。原地排序in-place冒泡排序只用到有限的额外变量比如用于交换的临时变量空间复杂度为 O(1)不依赖额外数组。稳定排序stable如果两个元素的数值相等它们在排序前后的相对顺序不会发生改变。原因是冒泡排序只交换“前一个大于后一个”的情况等于时不交换所以相等元素的先后关系保持不变。比较排序comparison-based通过元素之间的比较决定顺序是典型的两两比较排序。最坏时间复杂度 O(n^2)数组完全逆序时每一轮都要执行最多 n-1-i 次交换累计接近 n^2/2 次比较和交换。最好时间复杂度 O(n)数组已经有序且代码里加了“本轮没有发生交换就提前退出”的优化时只需要做一轮 n-1 次比较就能结束排序。平均时间复杂度 O(n^2)随机排列的数组期望上需要跑满约 n(n-1)/2 次比较。把这些特性背下来没有意义理解前面几个定义之间的联系才重要。稳定性和原地性决定了它在工业场景中几乎不会大规模使用而最好情况下的 O(n)又让它在“基本有序 规模不大”的特殊场景下依然有一席之地。后面我还会专门展开讲。2. 标准实现与多语言代码细节2.1 C 标准实现一步一步搭出框架先看最核心的 C 实现。下文这种写法参考了标准 C 的常规风格编译环境基于 C11 及以上完整、直接、可用。#include iostream #include vector using namespace std; void bubbleSort(vectorint arr) { int n arr.size(); for (int i 0; i n - 1; i) { bool swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { swap(arr[j], arr[j 1]); swapped true; } } if (!swapped) break; } } int main() { vectorint data {5, 1, 4, 2, 8}; bubbleSort(data); for (int x : data) cout x ; return 0; }这段代码里有几个细节值得反复咀嚼。首先外层循环的边界是i n - 1不是i n。仔细想一下当只剩最后一个元素还没有“被固定”时它必然已经是所有剩余元素中最小的不需要再单独比较。跑 n-1 轮就已经足够让 n-1 个较大元素各归其位最小的自然留在最左边。其次内层循环的边界是j n - 1 - i。随着轮次增加数组右侧的 i 个位置已经排好没有必要再去比较它们所以每轮都要减去 i。这个减法是冒泡排序效率略优于“傻循环”的关键也是新手最容易写错的地方。再者我在这里加了swapped标志。这是对经典冒泡排序最重要的优化。如果一整轮下来没有发生任何交换说明数组已经有序直接结束外层循环避免多余的比较。数据本来就基本有序时这个优化能把运行时间从 O(n^2) 降到 O(n)。2.2 C 语言版本回到裸指针视角C语言的实现思路和C一摸一样只是需要用指针和数组下标来操作同时手动完成交换。我也在代码中保留了提前退出机制。#include stdio.h void bubbleSort(int arr[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped 1; } } if (!swapped) break; } } int main() { int data[] {5, 1, 4, 2, 8}; int n sizeof(data) / sizeof(data[0]); bubbleSort(data, n); for (int i 0; i n; i) { printf(%d , data[i]); } return 0; }C语言的代码没有任何花哨之处但它特别适合用来理解底层过程。sizeof(data) / sizeof(data[0])是在栈上获取数组长度最常用的做法注意这招只对数组本身有效传入函数退化成指针后就不能再这么用了。这也是很多C语言新手踩过的坑在函数内用sizeof(arr)求长度结果只拿到一个指针的大小。这个问题我在做题和写业务代码时都遇到过必须提醒一句。2.3 Java 版本面向封装风格Java 写冒泡排序我一般直接用数组或者泛型列表。下面的代码用整数数组展示最清晰的裸逻辑public class BubbleSort { public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) break; } } public static void main(String[] args) { int[] data {5, 1, 4, 2, 8}; bubbleSort(data); for (int x : data) { System.out.print(x ); } } }Java 版本和 C 版本逻辑等价差别主要体现在类型和长度获取方式上。arr.length是属性而不是方法写arr.length()是新手常见的低级错误。另外如果处理的是Integer[]对象数组temp交换和基本类型数组没有任何区别但如果要用Arrays.sort的 Lambda 比较器就得依赖引用类型了。2.4 Python 版本最贴近自然语言Python 版本的冒泡排序有一个非常清爽的写法。这里也要注意 Python 的swap不需要中间变量用多重赋值即可实现def bubble_sort(arr): n len(arr) for i in range(n - 1): swapped False for j in range(n - 1 - i): if arr[j] arr[j 1]: arr[j], arr[j 1] arr[j 1], arr[j] swapped True if not swapped: break return arr data [5, 1, 4, 2, 8] print(bubble_sort(data))Python 的for j in range(n - 1 - i)和 C 的j n - 1 - i是一个意思range本身不包含右边界。这种写法在 LeetCode 演板时看起来非常简洁。不过要注意一点Python 列表里如果混入了None或者不同数据类型直接用比较会抛TypeError。做算法题时输入类型往往是纯数值但写业务脚本时得先做好数据类型校验或过滤。2.5 用表格横向对比四种语言写法的差异语言基本类型/对象交换方式长度获取是否支持提前退出写法Cvector 首选swap / 手写 temparr.size()supportCint arr[]手写 tempsizeof(arr)/sizeof(arr[0])supportJavaint[]手写 temparr.lengthsupportPythonlist多重赋值len(arr)support从这张表可以看到剥掉语法外壳冒泡排序在各语言里的核心逻辑完全一致。区别只在于你用什么方式表达“交换”和“取长度”。这其实也印证了一个观点算法思想与语言无关学算法时不要被某一门语言的写法绑死多换几种语言实现同一段逻辑对理解算法本质很有帮助。3. 复杂度演变与三大优化玩法3.1 比较次数和交换次数怎么算出来的我相信很多读者对“O(n^2)”这个结论记得滚瓜烂熟但真要被问到“具体比较多少次”就有点含糊了。这里用等差数列帮大家一次算清楚。先说比较次数。第一轮内层要比较 n-1 次第二轮要比较 n-2 次直到最后一轮比较 1 次。总比较次数就是(n-1) (n-2) ... 1 n(n-1)/2所以说平均和最坏情况下复杂度是 O(n^2)常数因子约为 1/2。再说交换次数。最坏情况数组完全逆序下每一次比较都会触发一次交换因此交换次数同样达到 n(n-1)/2。平均情况下大约有半数比较会发生交换所以交换次数约为 n(n-1)/4。不要小看交换的代价在元素类型很大的时候比如结构体数组交换两个对象的开销远大于一次比较。这也是为什么当数据规模较大时冒泡排序在工程上速度往往垫底。最好情况呢如果数组已经完全有序在加了提前退出优化的情况下只需要跑第一轮 n-1 次比较发现没有交换就直接返回。此时的复杂度是 O(n)。如果没有提前退出优化哪怕数组已经有序也要跑满 n(n-1)/2 次比较白白浪费大量时间。3.2 优化一提前退出标志这个优化前面代码里已经反复出现这里再单独拎出来说一次原因。冒泡排序的特点是如果某轮比较过程中没有发生任何元素交换就说明数组中任意相邻位置都是“前小后大”的关系这样的数组必然是整体有序的。实现方式很简单就是每轮开始时设置一个布尔标志swapped false只要发生交换就置为true本轮循环结束后检查这个标志如果仍然是false直接跳出外层循环。这个优化的价值在于它把最好情况下的时间从 O(n^2) 降到了 O(n)而且实现成本极低。实测下来对于 JIT 编译后的 Java 程序和解释执行的 Python 脚本这个优化都能显著减少无谓的循环迭代。特别在排序一个已经基本有序的数组时可能只需要跑两三轮就提前退出视觉效果非常明显。3.3 优化二记录最后发生交换的位置这个优化属于更进阶的玩法但也在很多面试题里被问过。思路是每一轮内层循环结束时记录下最后一次发生交换的下标lastIndex。这个下标之后的元素已经是有序的了下一轮的内层循环只需要跑到lastIndex即可不再需要跑到 n-1-i。为什么能这样因为如果从某个位置往后都没有再发生过交换说明该位置右侧的所有相邻元素都已经满足“前小后大”那部分已经整体有序。我举个例子数组[1, 2, 3, 5, 4, 6, 7]第一轮比较时只在5和4交换过一次最后交换的位置是下标 3。下标 3 之后的6、7已经是有序的下一轮完全没有必要再比较它们。实现代码如下以C为例void bubbleSortOptimized(vectorint arr) { int n arr.size(); int end n - 1; while (end 0) { int lastSwap 0; for (int j 0; j end; j) { if (arr[j] arr[j 1]) { swap(arr[j], arr[j 1]); lastSwap j; } } end lastSwap; } }这段代码把传统的双层 for 改成了 while 内部 for 的结构本质上是同一个算法但内层循环范围被动态压缩。当数组接近有序时lastSwap会快速前移几轮之内就缩减到 0效率大幅提升。3.4 优化三双向冒泡排序鸡尾酒排序双向冒泡排序是个冷门但很讨喜的改进版。传统冒泡排序每一轮只能把最大的数往右边“沉”一次鸡尾酒排序则是一轮里先去右边把大的放后面再去左边把小的放前面相当于左右两边同时固位。为什么要这么做考虑一个极端例子[2, 3, 4, 5, 1]。传统冒泡第一轮比较完1从左往右挪了 4 次才到第一位第一轮结束无非是把最大值5固定到最后。但第二轮之后仍然要花很久才能把整个数组调整好。鸡尾酒排序第一轮先向右冒泡把5送到最后接着立即向左冒泡把1送到最前。两轮就完成了排序比传统冒泡少跑不少轮次。代码实现也不复杂。C 版本如下void cocktailSort(vectorint arr) { int n arr.size(); bool swapped true; int start 0; int end n - 1; while (swapped) { swapped false; for (int j start; j end; j) { if (arr[j] arr[j 1]) { swap(arr[j], arr[j 1]); swapped true; } } if (!swapped) break; --end; swapped false; for (int j end - 1; j start; --j) { if (arr[j] arr[j 1]) { swap(arr[j], arr[j 1]); swapped true; } } start; } }这种优化特别适合“大部分元素已经有序只有少量小元素在右侧、大元素在左侧”的数据形态。实现上比传统冒泡复杂一点但思路并不难理解。如果你能在面试里说出这个优化并且解释清楚它的适用场景印象分会明显提升。3.5 优化后的复杂度变化速查版本最好时间平均时间最坏时间空间原始冒泡O(n^2)O(n^2)O(n^2)O(1)提前退出O(n)O(n^2)O(n^2)O(1)记录最后交换位置O(n)O(n^2)O(n^2)O(1)鸡尾酒排序O(n)O(n^2)O(n^2)O(1)注意这些优化改变的都是常数因子和最好情况理论上限没有被突破。冒泡排序依然是一个平方级算法面对十万级以上数据时和快速排序、归并排序的差距是数量级上的碾压。也正因为如此冒泡排序最适合去理解排序本质而不是处理海量数据。4. 常见问题与避坑排查实录4.1 边界条件写错内层循环越界这是初学者最常踩的坑。我见过不少新手写内层循环时用的是j n - 1哪怕外层已经到了i n - 2这样就会多比较一些已经有序的元素虽然不会导致数组越界或结果错误但会浪费时间而写成j n的人在比较arr[j] arr[j 1]时会让j 1访问到arr[n]这在 C/C 里就是越界访问在 Java 里直接扔ArrayIndexOutOfBoundsException。正确写法始终是j n - 1 - i理由我在前面已经讲得很清楚了。如果你怕自己记错可以直接套公式外层第 i 轮右侧已经有 i 个元素排好左侧待比较元素个数是 n - i这些元素之间只需要比较 n - i - 1 次因此内层循环条件就是j n - i - 1。这两者是等价的。4.2 提前退出标志放错位置很多人写提前退出时喜欢把标志放在内层循环的外面一层这没问题但是要注意逻辑。有些写法是每轮开头设置swapped false内层循环发生交换时置为true循环结束用if (!swapped) break。这个逻辑是对的。容易出错的写法是把标志初始化放在外层循环之前比如写成这样bool swapped false; for (int i 0; i n - 1; i) { swapped false; ... }这样写虽然最终结果也是对的但第一轮如果提前退出不会重新进入第二轮逻辑上没问题。问题出在有人把swapped初始化挪到外层循环外面后第二轮开始前忘记重置导致上一轮的交换状态延续到这一轮该退出的没退出。这里建议每次外层循环内部都重新初始化标志别怕写那一行。4.3 交换操作写错temp丢了原值手写交换的代码虽然基础但经常会因为粗心写错。最常见的错误是int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp;只要中间三行的顺序一乱就会把原值覆盖掉。比如有人会写成arr[j] arr[j 1]; arr[j 1] arr[j];这就等于把两个位置都赋值成了同一个值一个元素直接丢失。如果用 C 的std::swap或者 Python 的多重赋值这类低级错误可以完全避免。老实说我写过无数行冒泡排序唯一一次线上事故就是手写交换时顺序写错导致数据被覆盖。后来我养成一个习惯能用标准库的交换函数就不自己手写。4.4 稳定性被意外破坏冒泡排序理论上稳定但如果你把比较条件写成稳定性就没了。比如if (arr[j] arr[j 1]) swap(arr[j], arr[j 1]);这样做会导致相等元素发生交换相等元素的先后顺序随即反转。面试时如果要求稳定排序你必须严格使用而不是。这个细节也经常出现在“判断这个排序是否稳定”的考点旁边能准确答出比较条件对稳定性的影响是一个隐藏的加分项。4.5 数组几乎有序时的性能错觉我遇到过不少同学在做题时发现冒泡排序有时候挺快就会怀疑“是不是复杂度分析错了”。其实不是只是因为他们碰上了接近有序的数据提前退出优化发挥了作用。如果你拿一个完全随机的大数组比如十万个随机数去跑冒泡排序就会发现它比归并排序慢了上百倍。这种“小样本假象”也是初学者的常见误区。判断一个算法性能不要用眼睛盯随机的时间感受要看最坏复杂度和大规模实测。4.6 调试冒泡排序的一个实用技巧我早期调试排序算法时总喜欢在循环里到处打印数组状态后来发现最省事的方法是在每一轮外层循环结束后打印一次数组再标记出本轮要固定的位置。比如for (int i 0; i n - 1; i) { ... cout Round i : ; for (int x : arr) cout x ; cout endl; }这样你很快就能看到数组如何从无序逐步变为有序哪一轮没有发生交换哪个关键位置被固定下来。用少量输出定位逻辑错误比用调试器反复单步执行要快得多。这也是我在带新人时经常强调的技巧——先把过程可视化再去找错误。5. 冒泡排序的实战场景与算法选型对比5.1 面试与应试中的冒泡排序无论从哪个角度看冒泡排序都不是一个高效的排序手段但它依然高频地出现在笔试题和面试题里。原因有三第一它特别适合考察候选人对双重循环和边界条件的掌握第二通过询问是否知道优化点可以快速判断候选人的算法感知力第三它常常作为“改进其他排序算法”的起点。面试里常考的一种变体是“利用分治思想修改合并排序算法”这话说的其实是另一个方向的问题。分治思想的核心是递归拆分子问题典型代表是归并排序和快速排序。冒泡排序本身不属于分治因为它的比较过程并未拆分出独立的子区间。面试时如果被问到如何把分治思想运用在排序中应该转向归并排序的思路先把数组一分为二分别排序再合并结果而不是继续讨论冒泡排序。这一点千万别搞混很多初学者背熟了“快速排序”“归并排序”“冒泡排序”的类型划分但在现场一紧张就容易张冠李戴。我见过一个比较有意思的面试手撕题给定一个数组要求把所有偶数放到前面、奇数放到后面相对顺序不变。这个问题用冒泡排序的思想解非常漂亮——把比较条件从“前大于后”改为“前为奇且后为偶”就能保持稳定性地完成分类时间复杂度 O(n^2) 在小数据量下也能接受。这算是冒泡思想在非标准场景中的一次灵活应用。5.2 工程中到底什么时候用它既然冒泡排序这么慢我在实际工程里会用它吗答案是几乎不会大规模使用但有两个例外场景。第一个例外是数据量非常小比如 n 100且要求稳定排序。此时冒泡排序的常数开销极低代码也简单直白不易出bug。与其引入复杂的归并排序或者用系统排序但担心不稳定不如用冒泡排序写个两行逻辑。第二个例外是数据基本有序只需要做微调。比如某个缓存系统里的活跃度排名每天只变化几个位置其他元素的相对顺序基本不动。此时冒泡排序配合提前退出第一次遍历可能只要交换少量元素就完成排序实际耗时接近 O(n)比快排还省心。快排在处理基本有序数组时如果选点选得不好反而会退化到 O(n^2) 甚至栈溢出。其余场景包括规模稍大的普通数组排序我统一建议需要稳定排序内存充足 → 用归并排序内存紧张不需要稳定 → 用堆排序通用高性能需求 → 用系统自带的排序函数C 的std::sort、Java 的Arrays.sort、Python 的list.sort都是混合排序几乎总是最优选择5.3 与插入排序、选择排序的横向对比冒泡排序常被拿来和插入排序、选择排序放在一起对比因为它们都是简单的平方级排序算法且实现都很直观。插入排序的思路是“像理牌一样”把新元素插入到已经有序的前缀中。它在处理“几乎有序”的数据时表现极好接近 O(n)。选择排序则是每一轮从剩余元素中找最小的放到前面它的交换次数固定为 n-1 次但比较次数依然是 O(n^2)。三者相比之下冒泡排序的优势是稳定且容易理解劣势是交换次数多尤其在逆序场景下每轮几乎全部交换而这些交换又都是内存写操作比单纯比较昂贵。插入排序在绝大多数“小规模排序”场景下的实际运行速度优于冒泡排序原因是它的移动操作是“搬移”而非“交换”且当数据有序时能极早终止。选择排序则胜在交换次数少但稳定性差。如果你要给我的个人建议那就是在三者里我会优先学冒泡排序作为入门但在实际代码里小规模排序更常写插入排序。5.4 用可视化工具验证排序过程学冒泡排序不建议只对着代码看一定要亲手调试几轮动态过程。网上有很多排序可视化网站把数组大小和数据分布调到合适范围你会非常直观地看到每一轮最大值“冒”到右侧的过程。我自己带孩子学算法时就是先用可视化动画让他们看几十轮排序的彩色柱状图变化再让他们动手写代码。有了视觉记忆再理解“相邻元素交换”“右侧逐渐有序”这些描述就轻松得多。在本地实验时我建议写一段代码生成三种不同分布的数据随机、几乎有序、完全逆序分别统计冒泡排序的比较次数和交换次数你会立刻理解为什么最坏情况是逆序、最好情况是正序。这样得出的经验远比背复杂度公式更牢固。5.5 冒泡排序思想的延展冒泡排序虽然简单它的思想延展性很强。你会在很多地方看到它的影子例如对单链表进行排序时冒泡排序可以只改指针不改数据实现稳定排序在维护一个接近有序的增量数组时用冒泡思想做局部修正比重新排序更高效在某些嵌入式系统或驱动代码里受限于内存和平台环境不想引入递归和额外数组冒泡排序的实现成本最低并行计算中奇偶换位排序odd-even sort就是冒泡排序的并行版本核心交换逻辑一脉相承我这里说的“延展”并不是鼓励大家真的用冒泡排序扛业务而是提醒大家重视算法思想本身的迁移价值。理解了“相邻比较 交换”这个模型你再看奇偶排序、梳排序、以及某些局部调整算法时会有一种豁然开朗的感觉。写在最后的个人体会我做编程这么多年一直觉得算法学习最大的坑不是算法本身有多难而是很多人在入门阶段就急着去背“最优解”导致连一次完整的冒泡排序过程都没写过就去背快排模板了。我自己刚学排序算法时也犯过同样的毛病后来帮一个朋友调试越界 bug 时回头把冒泡排序重新写了几十遍才真正把它吃透。这里有个小小的经验分享每写一遍就换一种语言或者换一种优化策略或者在数组里故意构造逆序、重复、边界数据去观察它的运行表现。这样练上几次你会发现自己对排序算法的整体理解都提升了一个台阶。最后再分享一个我一直在用的小技巧面试前如果时间紧与其背一堆复杂算法的模板不如先把冒泡排序连同它的三种优化写在纸上同时把复杂度推导过程写在旁边。这不算投机取巧因为能把最基础的东西讲清楚讲透彻本身就是算法功底的体现。冒泡排序或许不是最快的、最强的但它绝对是最适合用来检验你是否真正理解排序过程的那面镜子。
返回列表