ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python手写冒泡排序:彻底理解列表升序排列

Python手写冒泡排序:彻底理解列表升序排列 冒泡排序是初学 Python 时最值得手写的排序算法之一。它要解决的问题很具体给定一个 Python 列表比如[64, 34, 25, 12, 22, 11, 90]通过反复比较和交换相邻元素把它变成从小到大排列的升序列表。Python 内置的list.sort()一行就能完成排序可真正让初学者卡住的往往不是“能不能排”而是“为什么相邻比较交换能够达成全局有序”“两层循环边界为什么这样写”“交换到底发生在哪个对象上”。这篇文章就以“列表升序排列”为目标从零实现冒泡排序再逐步加入提前结束优化、缩小比较范围、自动化验证和排错方法。最后你会发现冒泡排序的价值不只是排序那几行代码而是把比较、交换、不变量和循环边界这些基础概念一次讲透。1. 先想清楚“列表升序排列”和“冒泡排序”之间的联系1.1 “升序排列”到底在改什么列表元素的位置而不是变量的值用 Python 处理列表时最容易产生误解的地方是排序不只是在某个局部变量上做交换而是要修改列表对象内部每个位置上的元素。看下面这段代码a [3, 1, 2] a[0], a[1] a[1], a[0] print(a)输出是[1, 3, 2]。这里通过下标a[0]和a[1]同时取值、同时赋值把两个位置上的元素交换了。这个操作会影响列表外部可见的内容所以它叫原地修改。但下面这个写法不会改变列表def wrong_swap(a, b): a, b b, a nums [3, 1, 2] wrong_swap(nums[0], nums[1]) print(nums) # [3, 1, 2]没变化原因在于 Python 函数传参时的“赋值传递”。nums[0]和nums[1]作为实参传入函数后函数内部只是让局部变量a、b互相换了指向并没有对列表下标做任何写操作。升序排列的目标是让列表中原本[64, 34, 25]这类顺序变成[25, 34, 64]也就是让每一个位置上的元素满足左边的元素小于等于右边的元素。要达成这个目标必须通过下标操作列表本身而不是光靠变量交换。1.2 冒泡排序的直观过程最大值像气泡一样向右移动冒泡排序的核心思想可以这样描述从列表开头开始依次比较相邻的两个元素。如果左边元素大于右边元素就交换它们的位置。这一轮从左向右走完之后整个列表中的最大值一定会被移动到最后一个位置。重复这个过程但下一轮不再处理已经排好的末尾位置。因为每完成一轮末尾就确定一个全局最大值。经过多轮之后列表就变成升序。举例来说对[40, 10, 30, 20]执行第一轮冒泡40 和 10 比较40 10交换 - [10, 40, 30, 20] 40 和 30 比较40 30交换 - [10, 30, 40, 20] 40 和 20 比较40 20交换 - [10, 30, 20, 40]第一轮结束后最大值 40 已经被推到末尾。下一轮比较时就不再需要让 40 参与。“较大的值逐渐移到末尾”这个动作很像气泡从底部往上浮所以称为冒泡排序。这个形象化理解很重要它直接决定了第二轮循环需要缩小的范围已经浮到末尾的大元素不需要再比较。1.3 排序算法中的两个关键动作比较与交换冒泡排序每一轮都在重复两个动作比较相邻两个元素的大小。在符合条件时交换它们的位置。升序排列的比较规则是左边元素大于右边元素才需要交换。如果用伪代码表达这段逻辑是这样的if left right: 交换 left 和 right 的位置如果条件写反比如写成left right排序结果就会变成降序。如果条件写成left right排序虽然也能完成但会破坏冒泡排序的稳定性。稳定性这个概念后面会展开讲。在真实列表上不能直接用上面的伪代码完成交换必须使用索引if numbers[j] numbers[j 1]: numbers[j], numbers[j 1] numbers[j 1], numbers[j]只有真正操作列表下标列表内容才会改变。2. 运行环境确认之后第二件事是吃透列表的索引与交换2.1 用一条命令确认 Python 运行环境本文的代码只需要 Python 标准解释器不需要额外安装第三方库。比较新的 Python 3 版本都可以运行。打开终端或命令行输入python --version如果系统提示找不到python命令可以试python3 --version能正常打印出版本号就说明环境已经准备好。例如Python 3.12.4在 Windows 上也可以从开始菜单启动 Python 自带的 IDLE直接把代码粘贴运行。所有示例都围绕列表原地排序展开因此不管用命令行还是 IDE效果都一样。2.2 Python 列表基础操作速查冒泡排序代码虽然不长但会频繁用到列表的索引读取、元素赋值、切片复制和末尾追加。先把这些操作整理成一张速查表后面写代码时会顺畅很多。目标写法效果与说明创建列表nums [64, 34, 25]创建一个包含 3 个元素的列表读取元素nums[0]返回下标 0 位置的元素倒数读取nums[-1]返回最后一个元素不改变列表切片备份backup nums[:]生成一个独立的新列表修改元素nums[0] 100把下标 0 处替换为 100交换两个元素nums[0], nums[1] nums[1], nums[0]同时完成读取与赋值尾部追加nums.append(30)在列表末尾增加元素nums[:]这种切片写法在冒泡排序调试中非常实用。它可以在排序前保留原列表副本方便排序后做对比。注意Python 列表保存的是“对象引用”。执行backup nums[:]会得到一个新的列表对象但新列表里的元素仍指向原来的对象。对整数这类不可变对象来说这个细节不影响日常使用但理解这一点对排查“为什么原列表没变”非常关键。2.3 准备一组测试数据并明确就地排序与返回新列表的区别演示排序时建议准备一组有代表性的数据例如before [64, 34, 25, 12, 22, 11, 90]这份数据里有两位数、有个位数也有 90 这种明显偏大的数足够观察排序过程。实现冒泡排序前要先明确代码采用哪种策略策略 A直接修改传入的列表排序完成后不需要返回值。策略 B复制一份新列表在新列表上排序返回新列表不影响原列表。冒泡排序经典的实现方式是策略 A也就是就地排序。因为排序过程只需要两个相邻位置的辅助空间不需要分配一个大数组。后面所有主要版本的函数都采用这个策略同时返回原列表对象便于链式调用和验证。3. 第一版实现两层 for 循环完成一次真正的升序3.1 第一版代码不借助内置 sort把排序过程完整写出来第一版的目标是把逻辑写对不去做任何性能优化。新建一个bubble_sort_demo.py文件输入以下代码def bubble_sort_first(numbers): n len(numbers) for i in range(n - 1): for j in range(n - 1 - i): if numbers[j] numbers[j 1]: numbers[j], numbers[j 1] numbers[j 1], numbers[j] return numbers if __name__ __main__: before [64
返回列表