ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

冒泡排序算法详解:从VB课件到Python/C实现与优化

冒泡排序算法详解:从VB课件到Python/C实现与优化 简介冒泡排序算法是编程教育中最早接触的排序方法之一这份PPT课件专为教师课堂讲授和初学者自学设计。课件从生活场景切入以“明日之星英语演讲大赛”选手成绩排序为真实导入借助扑克牌排列和数组示意图清晰展示相邻元素比较交换、较大值逐轮“冒泡”至末尾的完整过程并逐步给出VB程序代码与双重循环控制方法。文件仅1个为pptx格式压缩包大小156KB方便直接下载使用。已有190人浏览学习。除基础原理和核心代码外课件还包含算法时间复杂度与空间复杂度分析、稳定排序特性说明以及设置标志位提前结束排序等常见优化思路可帮助学习者从原理到实现快速建立系统认知适合配合课堂讲解、课后自学或作为教师备课参考。1. 冒泡排序一堂评分课背后的 O(n²) 教学样本“明日之星英语演讲大赛”的评分表出来了要从几十名选手里挑出每组前三名。人工排序太慢写程序排又不知道从哪下手——这正是冒泡排序最典型的出场场景。这份《冒泡排序算法PPT课件.pptx》是一份14页的教学型课件用 VB 语言把冒泡排序从原理讲到代码实现场景设定就是大赛评分后的自动排序。它不追求效率极致而是把数组存储、双层循环、相邻交换这些基础概念拆开揉碎适合刚接触算法的学生也适合给新人做入职培训的老师直接拿去用。我今天把它拆开讲讲它的实现思路、代码细节以及新手最容易在哪几个地方翻车。2. 从牌面到代码把“小数上浮”翻译成双层循环2.1 那张五张牌的图其实已经把算法讲完了课件第 6 页用五张扑克牌演示排序过程这比直接甩代码友好得多。它模拟的是棋牌规则从左到右依次比较相邻两张牌如果左边比右边大就交换位置。第一轮比较完最大的牌被推到最右边第二轮比较完第二大的牌被推到倒数第二个位置以此类推。这个过程的直观想象是把数组的一端当水底另一端当水面较小的数据不断向上浮较大的数据向下沉。所以叫“冒泡”。课件的关键点在第 11 页的代码框架For i 0 To 3 For j 4 To i 1 Step -1 If A(j) A(j - 1) Then 交换 A(j) 和 A(j - 1) End If Next j Next i这段代码针对的是 A(0) 到 A(4) 五个元素。外层循环 i 从 0 到 3控制的是“轮数”。五张牌排好序最多需要四轮。内层循环 j 从 4 递减到 i 1控制的是“第 i 轮需要比较哪些相邻位置”。注意这里用的是Step -1也就是从数组尾部往头部走。这种写法在算法教材里不常见但非常适合初学 VB 的人理解“从最后一个元素开始依次和前面的比”。2.2 i 和 j 的边界为什么这样定很多人看不懂 j 的终止条件i 1这其实是对“已经排好的部分不再碰”的代码化表达。第一轮 i 0 时j 从 4 跑到 1比较了 A(4) 和 A(3)、A(3) 和 A(2)、A(2) 和 A(1)、A(1) 和 A(0)一共四次比较。这一轮结束最小值已经“浮”到了 A(0) 的位置。第二轮 i 1 时j 从 4 跑到 2只比较 A(4) 和 A(3)、A(3) 和 A(2)、A(2) 和 A(1)一共三次。A(0) 已经是全局最小不再参与比较。第三轮、第四轮同理。四轮下来总比较次数是 4 3 2 1 10 次。对于五个元素这正好是 C(5,2) 10每一对元素都被比较过一次。我把每一轮 j 的取值范围整理成一张表方便对照轮数 ij 起始值j 终止值比较次数比较的元素对0414(4,3)(3,2)(2,1)(1,0)1423(4,3)(3,2)(2,1)2432(4,3)(3,2)3441(4,3)为什么从尾部开始因为Step -1配合A(j) A(j - 1)是把较小的值往前推。如果从头部开始用A(j) A(j 1)那就是把较大的值往后推。两种方向都能排但课件选的是“小数上浮”这一视觉模型和“冒泡”这个比喻完全对齐。2.3 为什么说这是稳定的排序冒泡排序是稳定排序。意思是两个相等的元素排序后它们的相对位置不会改变。原因在于交换条件用的是严格小于而不是小于等于。当 A(j) 和 A(j-1) 相等时不触发交换。这个细节在课件里没刻意强调但它决定了算法是否稳定。如果你把改成相等的元素会被来回交换稳定性就丢了。这在很多应用场景里很重要。比如排序对象是学生成绩表每行数据还带着姓名和组别如果两个学生同分你希望排序后他们原来的先后顺序不变那冒泡排序用就对了。3. 完整代码实现从 VB 到 Python 的三种写法3.1 课件里的 VB 代码逐行拆解课件第 12 页补上了交换逻辑完整代码是For i 0 To n - 1 For j n To i 1 Step -1 If A(j) A(j - 1) Then k A(j) A(j) A(j - 1) A(j - 1) k End If Next j Next i逐行说明k A(j)先拿一个临时变量 k 存住 A(j) 的值。这一步是交换的“空杯子”没有它下一步 A(j) A(j - 1) 会把 A(j) 原来的值覆盖掉。A(j) A(j - 1)把前一个元素的值赋给当前元素。A(j - 1) k把临时变量里存的原 A(j) 值赋给前一个元素。这里的边界条件是随数组长度变化的。课件里的例子是 A(0) 到 A(4)所以外层是For i 0 To 3也就是n - 1。如果你换成 A(1) 到 A(5) 的数组外层循环就得从 1 开始For i 1 To n - 1 For j n To i 1 Step -1 If d(j) d(j - 1) Then k d(j) d(j) d(j - 1) d(j - 1) k End If Next j Next i课件第 13 页专门对比了这两种下标写法A(n) 和 d(1 to n)。这是 VB 里数组声明的两种风格。关键是理解i 的起始值要和数组下标的最小值对齐j 的终止值要对齐到 i 1这个逻辑和下标起点无关。3.2 Python 移植版逻辑相同写法更简洁如果要在现代环境里演示冒泡排序Python 是最自然的选择def bubble_sort(arr): n len(arr) for i in range(n - 1): for j in range(n - 1, i, -1): if arr[j] arr[j - 1]: arr[j], arr[j - 1] arr[j - 1], arr[j] return arr # 用课件里的五个成绩做测试 scores [87, 92, 78, 90, 85] sorted_scores bubble_sort(scores) print(sorted_scores)参数说明range(n - 1)外层循环 n-1 轮。五个元素跑四轮和 VB 版本的For i 0 To 3一一对应。range(n - 1, i, -1)内层循环等价于 VB 的For j n To i 1 Step -1从数组尾部递减到 i 1。arr[j], arr[j - 1] arr[j - 1], arr[j]Python 的元组解包交换不需要临时变量。底层还是三步交换只是语法层面帮你省了。这里有个容易误解的地方Python 的range是左闭右开range(n - 1, i, -1)实际取到的 j 值是 n-1, n-2, ..., i1。比如 n5、i0 时j 取 4、3、2、1正好对应四次比较。3.3 C 语言版指针视角下看交换的本质C 语言版能帮你理解交换到底发生了什么void bubble_sort(int arr[], int n) { for (int i 0; i n - 1; i) { for (int j n - 1; j i; j--) { if (arr[j] arr[j - 1]) { int temp arr[j]; arr[j] arr[j - 1]; arr[j - 1] temp; } } } }C 版和 VB 版的逻辑完全一致只是j i对应 VB 的to i 1。为什么不是j i因为当 j i 时arr[j]和arr[j - 1]的下标是 i 和 i-1而 arr[i-1] 在上一轮已经排好了不需要再比较。多比较一次虽然不会出错但属于无用功而且如果 i 0 时 j 0arr[-1]直接越界。这里的int temp就是 VB 里的k是整个交换逻辑的核心。很多新手写 C 的冒泡会直接arr[j] arr[j-1]然后arr[j-1] arr[j]结果两个元素都变成了同一个值——因为没有先保存被覆盖的值。3.4 三种语言的复杂度对照语言外层循环内层循环交换方式适用场景VBFor i 0 To n-1For j n To i1 Step -1临时变量 k教学演示、VB 课程设计Pythonrange(n-1)range(n-1, i, -1)元组解包算法理解、小规模数据排序Cfor i 0; i n-1; ifor j n-1; j i; j--临时变量 temp嵌入式场景、性能敏感场景时间复杂度和空间复杂度在三种语言下完全一致最坏和平均都是 O(n²)最好情况 O(n)下一章优化后会达到空间复杂度 O(1)。课件里用等差数列求和推导出比较次数为 n(n-1)/2对应代码里的双层循环总迭代次数。4. 冒泡排序常见问题与避坑五条真实排错记录4.1 外层循环多跑一轮结果没错但多了一轮无用功现象明明 n 个元素只需要 n-1 轮有人写成For i 0 To n循环体跑完也没报错但最后几轮全是无效比较。原因第 n 轮时内层循环 j 从 n 递减到 i1 n1由于 j 的起始值 4 已经小于终止条件VB 的For循环直接跳过。结果没错但你的程序比标准实现多执行了一层空循环白白消耗 CPU。解决写代码前先算清楚五个元素排四轮n 个元素排 n-1 轮。最稳妥的验证方法是在代码里加一个计数器统计内层循环实际执行次数。如果 n5 时比较次数超过 10 次说明边界写宽了。4.2 内层循环 j 的终止条件写成 0导致重复比较现象把For j 4 To i 1 Step -1写成For j 4 To 0 Step -1程序能跑但每一轮都在重复比较已经排好的部分。原因j 的终止条件没和 i 关联。当 i 1 时A(0) 已经是最小值你不应该再去碰它。但 j 跑到 0 的话A(1) 和 A(0) 还会再比较一次。这只是浪费还不算致命。如果数组下标从 1 开始而你写成了 j 0那A(j-1)会访问到 A(-1)直接越界崩溃。解决记住 j 的终止条件必须和 i 绑定把“已排好区域”排除在比较范围外。正确关系是 j 最小到 i 1对应数组下标最小值到 i 1 的区间。4.3 交换逻辑忘记临时变量数据直接丢失现象输出结果里出现两个相同的数字另一个数字凭空消失。比如数组 [5, 3] 排完变成 [3, 3]。原因交换写成了A(j) A(j - 1) A(j - 1) A(j)第一步执行后A(j) 原来的值被 A(j-1) 覆盖第二步把已经覆盖后的值又赋回去两个位置都变成了原来的 A(j-1)。这就是为什么需要临时变量 k —— 它像交换两杯水时需要第三个空杯一样。解决三步交换缺一不可。k A(j)A(j) A(j-1)A(j-1) k。顺序也不能换先把 A(j) 存起来再覆盖最后赋值回去。4.4和的稳定性差异现象对带编号的记录排序排序后相同成绩的编号顺序乱了。原因交换条件用了而不是。当两个元素相等时会触发交换把后面的元素换到前面来破坏了稳定性。课件里的 VB 代码用的是这是正确的。解决需要保持稳定排序时严格使用。如果你发现自己排序后相同元素的先后顺序变了先去查这个条件多半是这里出了问题。4.5 直接套用冒泡排序java的模板没改数组下标现象从网上复制一段 Java 的冒泡排序公式是for (int i 0; i arr.length - 1; i)但你的数组下标是 1 到 n直接套用后第一个元素永远不参与排序。原因不同语言的数组下标起点不同。VB 的数组可以声明为Dim d(1 To 5)下标从 1 开始但 Java、C、Python 的数组下标一律从 0 开始。模板代码的边界是写死的不适用于所有场景。解决确定你的数组下标起点再调整循环边界。下标从 1 开始时外层循环从 1 到 n-1内层循环从 n 到 i1。下标从 0 开始时外层从 0 到 n-2内层从 n-1 到 i1。两个版本差一个偏移量踩坑的人特别多。5. 优化后与复杂度实测把 O(n²) 压到最好 O(n)5.1 加一个标志位检测“提前有序”的场景冒泡排序最让人诟病的是不管数据是否已经有序都要走完 n-1 轮。实际上如果某一轮没有任何交换发生说明数组已经有序可以立即终止。Dim swapped As Boolean For i 0 To n - 1 swapped False For j n To i 1 Step -1 If A(j) A(j - 1) Then k A(j) A(j) A(j - 1) A(j - 1) k swapped True End If Next j If Not swapped Then Exit For Next i这个优化在数据基本有序时效果显著。比如数组 [1, 2, 3, 4, 5]第一轮跑完发现一次交换都没有直接退出时间复杂度从 O(n²) 降到了 O(n)。课件里的五张牌场景如果运气好摸到接近有序的牌这个优化能让程序提前两轮结束。5.2 记录最后一次交换位置缩小下一轮比较区间每次交换都发生在数组的不同位置。最后一次发生交换的位置之后所有元素都已经排好下一轮不需要再比较这个位置之后的元素。Dim lastSwap As Integer Dim innerEnd As Integer innerEnd n For i 0 To n - 1 lastSwap 0 For j innerEnd To i 1 Step -1 If A(j) A(j - 1) Then k A(j) A(j) A(j - 1) A(j - 1) k lastSwap j End If Next j innerEnd lastSwap If innerEnd 0 Then Exit For Next i这个优化的逻辑是如果这一轮最后一次交换发生在位置 j那么 j 之后的所有元素都已经有序下一轮内层循环只需要跑到 j 就行。配合标志位一起用效果比单独的标志位更好特别是对“前半部分乱序、后半部分有序”的数据。5.3 用随机数组验证正确性我一般会在写完排序后用一个随机数组做验证。正确性测试不能只靠“看结果对不对”要固定随机种子跑多组数据对比import random def bubble_sort(arr): n len(arr) for i in range(n - 1): swapped False for j in range(n - 1, i, -1): if arr[j] arr[j - 1]: arr[j], arr[j - 1] arr[j - 1], arr[j] swapped True if not swapped: break return arr # 100 组随机数据做验证 for _ in range(100): test [random.randint(1, 1000) for _ in range(20)] sorted_test bubble_sort(test[:]) assert sorted_test sorted(test), ffailed: {test} print(100 组随机数据全部通过)这段代码里random.randint(1, 1000)生成了 20 个 1 到 1000 的随机成绩sorted(test)是 Python 内置排序作为“标准答案”断言不通过就打印出哪组数据出问题。跑通了再放到真实项目里基本不会翻车。对于想验证复杂度的人还可以改写代码来统计比较次数和交换次数分别在内外循环各放一个计数器。你会看到随机数据下比较次数接近 n(n-1)/2优化后的有序数据下比较次数只有 n-1 次差距非常直观。5.4 课件数据实测72、85、90、68、79 的完整排序轨迹用课件同款五元素数组做个手动推演第一轮j4 比较 79 和 68交换数组变 [72, 85, 90, 68, 79]j3 比较 68 和 90交换变 [72, 85, 68, 90, 79]j2 比较 68 和 85交换变 [72, 68, 85, 90, 79]j1 比较 68 和 72交换变 [68, 72, 85, 90, 79]。最小值 68 到达数组头部。第二轮j4 比较 79 和 90交换数组变 [68, 72, 85, 79, 90]j3 比较 79 和 85交换变 [68, 72, 79, 85, 90]j2 比较 79 和 72不交换。此时 72、79、85、90 已经有序。第三轮j4 比较 90 和 85不交换j3 比较 85 和 79不交换。这一轮没有发生任何交换标志位触发程序提前结束。这个例子很好地展示了“数据接近有序时冒泡排序并不慢”的特点。它只用两轮半就完成了排序比最坏情况省了将近一半的比较。从那以后我每次讲冒泡排序都会先问一句你的数据是什么状态的随机乱序就用最基础的双层循环接近有序就把标志位和 lastSwap 两个优化都加上。教学演示用 VB 版学生能看清每一步交换实际项目用 Python 版或 C 版干净利落。这份课件的价值就在于此——它不是一个高效的排序算法但它是一个让你彻底理解“比较-交换-循环边界”这三个核心概念的最佳样本。希望帮到你。本文还有配套的精品资源点击获取
返回列表