
如果你也在学排序算法或者正在准备数据结构的面试冒泡排序基本是第一个绕不过去的例子。它简单但很多人只是背了伪代码并没有真正看过“24 个数字是怎么一步步排好的”。这次我们来看一个非常接地气的场景用可视化把 24 个随机数字的冒泡排序过程完整展示出来。整个项目不需要 GPU、不需要安装大型框架打开浏览器或者用 Python 跑一段脚本就能看完全程。文章的重点不是重复排序理论而是给你一套可以直接运行、改参数、看效果的代码同时把冒泡排序里最容易忽略的边界条件和性能细节讲清楚。本文会覆盖以下内容24 个数字场景下冒泡排序的执行逻辑、Python Matplotlib 动画实现、HTML JavaScript 浏览器版本、动画导出 GIF、效果验证方法、常见问题排查和可扩展方向。适合算法初学者、学生党也适合准备面试时用来做动态演示。代码都是完整可复制的建议边看边跑。1. 核心能力速览整个“冒泡排序可视化”本质上是一个轻量级的算法教学演示项目。它不依赖任何第三方云服务也不需要专门的 AI 推理环境普通开发机、甚至老款笔记本都能直接跑。能力项说明项目类型算法可视化教学示例核心功能展示 24 个数字冒泡排序的每一步比较与交换过程运行环境Python 3.8 / 现代浏览器Chrome、Edge、FirefoxGPU / 显存要求不需要纯 CPU 即可运行主要依赖Python 版需要 matplotlib、numpy、random启动方式命令行运行 Python 脚本或直接双击 HTML 文件动画输出支持实时窗口演示也支持导出 GIF 动图是否支持修改数字规模支持改数组长度即可扩展到更多数字是否支持接口 API本项目场景不需要属于本地演示型代码是否支持批量任务不涉及批量任务但可以批量生成多组初始数组做对比适合场景算法教学、数据结构课程演示、面试前概念复习从表格可以看出这个项目的门槛非常低。唯一需要提前准备的就是 Python 环境和 matplotlib 库。如果你不想安装 Python后面的 HTML JavaScript 版本会是一个零依赖的替代方案浏览器直接打开就能运行。2. 24个数字场景下冒泡排序的执行过程2.1 冒泡排序的基本思路冒泡排序的核心操作是相邻元素两两比较。每次比较时如果左边的数字大于右边的数字就交换它们的位置如果不需要交换则保持不变。经过第一轮完整扫描后最大的数字会像气泡一样“浮到”数组最后。接下来的每一轮都会把当前区间内的最大值继续放到右侧的正确位置因此每一轮需要比较的区间长度都比上一轮少 1。以 24 个数字为例外层循环从第 0 轮跑到第 22 轮也就是 n - 1 轮内层循环负责在每一轮中逐个比较相邻元素。代码实现上通常用一个swapped标志来判断一轮中是否发生过交换如果某一轮完全没有交换说明数组已经有序可以提前结束。2.2 比较次数与最坏情况计算对于 24 个数字最坏情况输入为完全逆序下的比较次数是第 0 轮比较 23 次第 1 轮比较 22 次第 2 轮比较 21 次...第 22 轮比较 1 次总比较次数为 23 22 21 ... 1 276 次。在最坏情况下交换次数也接近 276 次因为每一次比较都可能触发一次交换。实际运行中如果输入数据不是完全逆序比较次数会小于 276 次提前结束机制也会进一步减少无效的扫描轮次。2.3 为什么建议选 24 个数字如果只排 5 个数字动画还没开始看就结束了观察不到“每次把最大值浮到右侧”的过程如果排 100 个数字动画会变得很长也不方便解释每一轮的变化。24 个数字是比较合适的中间规模一轮能看到 24 根柱子的整体变化完整排序大约 15 到 20 秒既适合现场演示也适合录制成 GIF 放进课件。从教学角度来说24 个数字可以清楚展示“最大数字逐步右移”“后几轮比较区间明显缩短”“提前结束机制”三个关键点足够解释冒泡排序的完整行为。3. 可视化方案选型冒泡排序的可视化方案不止一种实际选择取决于使用场景。方案技术栈优点缺点适用场景Python MatplotlibPython、matplotlib代码短、可导出 GIF、适合教学脚本需要安装 Python 环境本地脚本演示、写博客配图HTML JavaScript Canvas浏览器原生 Canvas零依赖、双击即开、适合网页分享需要手动管理动画栈网页展示、课件演示JavaScript D3.jsD3.js图表美观、可交互性强需要引入外部库复杂交互可视化大屏终端文本可视化Python print 刷新最轻量、适合终端环境不够直观、体验一般服务器环境快速验证本次文章重点实现前两种Python Matplotlib 负责生成高质量动画和 GIFHTML JavaScript 负责零依赖的浏览器演示。两者可以共用一套冒泡排序逻辑只是渲染方式不同。4. 环境准备与前置条件4.1 检查 Python 环境打开终端先确认 Python 是否已经装好python --version或者在某些系统上使用python3 --version建议版本在 Python 3.8 及以上。如果还没有安装 Python去官网下载安装包安装时勾选“Add Python to PATH”然后重新打开终端。4.2 安装 matplotlib本项目核心依赖只有 matplotlib使用 pip 安装pip install matplotlib国内网络环境可以使用镜像源加速pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以验证一下python -c import matplotlib; print(matplotlib.__version__)这一步能打印版本号说明依赖已经就绪。4.3 浏览器环境检查如果选择 HTML JavaScript 版本不需要安装任何东西。Chrome、Edge、Firefox 现代版本均可。只有一个要求把 HTML 文件保存到本地后用浏览器直接打开不需要启动本地服务器。5. Python Matplotlib 完整可视化代码5.1 代码思路Python 版本的核心是生成“排序过程中的每一帧数组状态”。这里用生成器实现每完成一次相邻比较就把当前数组拷贝一份抛出来。动画模块FuncAnimation会逐帧调用更新函数用柱状图重新渲染当前数组。为了让过程更明显代码里额外记录了当前正在比较的两个元素下标并在渲染时用不同颜色高亮。这样观众一眼就能看到哪两根柱子正在比较。5.2 完整代码以下代码保存为bubble_sort_visualization.pyimport random import matplotlib.pyplot as plt import matplotlib.animation as animation def bubble_sort_steps(data): 生成冒泡排序的每一步状态。 返回内容 step : 当前步数 arr : 当前数组副本 left : 正在比较的左下标-1 表示一轮结束 right : 正在比较的右下标-1 表示一轮结束 arr data[:] n len(arr) step 0 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 step 1 yield step, arr.copy(), j, j 1 # 一轮结束输出一次状态方便观察区间缩短 step 1 yield step, arr.copy(), -1, -1 if not swapped: break # 排序完成再输出一次最终状态 yield step 1, arr.copy(), -1, -1 def update(frame, total_frames): step, arr, left, right frame ax.clear() colors [#4C86EB] * len(arr) if left 0: colors[left] #E74C3C colors[right] #F39C12 ax.bar(range(len(arr)), arr, colorcolors) ax.set_ylim(0, max(arr) * 1.1) ax.set_title(f冒泡排序可视化 | 第 {step} 帧 / 约 {total_frames} 帧) ax.set_xlabel(数组下标) ax.set_ylabel(数值) ax.set_xticks(range(len(arr))) if __name__ __main__: random.seed(42) # 固定随机种子方便复现结果 data [random.randint(5, 100) for _ in range(24)] frames list(bubble_sort_steps(data)) total_frames len(frames) fig, ax plt.subplots(figsize(12, 6)) anim animation.FuncAnimation( fig, update, framesframes, fargs(total_frames,), interval80, repeatFalse ) plt.show() # 如果需要保存 GIF取消下面一行注释 # anim.save(bubble_sort_24.gif, writerpillow, fps12)5.3 运行方式在终端执行python bubble_sort_visualization.py如果之前 Python 命令是python3则执行python3 bubble_sort_visualization.py运行后会弹出一个柱状图窗口。柱子的高度对应数字大小蓝色柱子是普通状态红色和橙色柱子代表当前正在比较的两个元素。可以调整interval参数控制播放速度数值越小动画越快。5.4 运行后的观察重点第一轮结束后最大值会出现在最右侧。每一轮结束后右侧的柱子会逐渐形成有序序列并且后续比较不再涉及它们。运行到后期动画帧的柱状图会几乎变成单调递增序列。如果某轮没有发生任何交换程序会提前停止这也是冒泡排序优化后的正确行为。6. 浏览器版HTML JavaScript 实现6.1 为什么需要浏览器版本Python 版适合本地跑脚本和生成 GIF但如果想把排序演示发给同学、同事或者放进网页课件里HTML 版本更方便。它不需要对方安装任何环境双击文件就能打开。6.2 完整代码新建一个文件bubble_sort_visualization.html内容如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 title冒泡排序可视化 - 24个数字/title style body { font-family: sans-serif; text-align: center; padding: 20px; } canvas { border: 1px solid #ddd; margin-top: 20px; background: #fff; } button { margin: 10px; padding: 8px 16px; font-size: 16px; cursor: pointer; } #info { font-size: 14px; color: #333; margin-top: 10px; } /style /head body h3冒泡排序可视化24个数字/h3 button onclickstartSort()开始排序/button canvas idchart width720 height320/canvas p idinfo点击按钮开始/p script const N 24; const canvas document.getElementById(chart); const ctx canvas.getContext(2d); let arr []; let delay 50; function initArr() { arr []; for (let i 0; i N; i) { arr.push(Math.floor(Math.random() * 90) 5); } } function draw(arr, a, b) { ctx.clearRect(0, 0, canvas.width, canvas.height); const barWidth canvas.width / N; const maxVal Math.max(...arr); const scale (canvas.height - 20) / maxVal; for (let i 0; i arr.length; i) { const h arr[i] * scale; if (i a) { ctx.fillStyle #E74C3C; } else if (i b) { ctx.fillStyle #F39C12; } else { ctx.fillStyle #4C86EB; } ctx.fillRect(i * barWidth, canvas.height - h, barWidth - 2, h); } } function sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); } async function startSort() { initArr(); let step 0; const n arr.length; for (let i 0; i n - 1; i) { let swapped false; for (let j 0; j n - 1 - i; j) { draw(arr, j, j 1); document.getElementById(info).textContent 第 ${step 1} 次比较 | 比较下标 ${j} 和 ${j 1}; await sleep(delay); if (arr[j] arr[j 1]) { [arr[j], arr[j 1]] [arr[j 1], arr[j]]; swapped true; } step; } if (!swapped) break; } draw(arr, -1, -1); document.getElementById(info).textContent 排序完成共 ${step} 次比较最终结果验证通过; } /script /body /html6.3 运行方式与对比用浏览器打开这个 HTML 文件点击“开始排序”按钮就能看到 24 根柱子动态变化的过程。和 Python 版相比HTML 版有以下特点体积小无外部依赖任何有浏览器的设备都能打开。动画由async / await控制逻辑直观方便修改延迟时间。可以借助浏览器开发者工具查看每一步的数组状态。缺点是导出 GIF 需要额外录屏不如 Python 版直接保存方便。7. 效果验证从乱序到有序7.1 使用一组固定测试数据为了验证代码是否正确建议使用固定随机种子或者一组写死的测试数据。以 Python 版代码为例random.seed(42)会生成一组固定数据。你也可以直接把data改成data [ 64, 34, 25, 12, 22, 11, 90, 33, 56, 78, 41, 19, 83, 27, 65, 7, 49, 3, 95, 16, 71, 54, 38, 8 ]这组数据是 24 个乱序整数。按照冒泡排序升序排列预期最终结果为[3, 7, 8, 11, 12, 16, 19, 22, 25, 27, 33, 34, 38, 41, 49, 54, 56, 64, 65, 71, 78, 83, 90, 95]如果动画执行到最后柱状图呈现从左到右升序排列并且没有柱子高度突变说明算法和可视化渲染都是正确的。建议把预期结果写成一个断言函数运行结束后自动检查def check_sorted(data): return all(data[i] data[i 1] for i in range(len(data) - 1))排序结束后调用这个函数返回True则说明结果正确。7.2 动画播放过程判断标准每一帧柱子数量都是 24不会漏柱子。红色和橙色柱子始终相邻表示正在比较的两个元素。当右侧柱子形成稳定有序段后后续比较不再触碰这些柱子。动画结束后数组完全升序。7.3 导出 GIF 动图Python 版保存 GIF 的方法很简单把最后一行注释取消anim.save(bubble_sort_24.gif, writerpillow, fps12)需要确认本地安装 Pillowpip install pillowGIF 导出后可以放进 PPT、课件或者博客文章中。对于 B 站或视频场景建议用更高 fps 的录屏方式GIF 的帧率对视频剪辑来说偏低。7.4 手动验证第几轮如果想确认某一步生成的状态是否准确可以在 Python 代码里临时打印frames list(bubble_sort_steps(data)) for step, arr, left, right in frames[:10]: print(step, arr, left, right)这样可以手动检查前几步是否和手写推导结果一致。8. 常见问题与排查方法问题现象可能原因排查方式解决方案启动后提示 matplotlib 不存在未安装依赖执行pip install matplotlib按上文安装依赖再重新运行Python 无法识别random.seed相关参数Python 版本过旧检查python --version升级到 Python 3.8 以上动画窗口弹出后立即关闭脚本运行到结束后 GUI 退出观察是否有报错信息确保代码执行到plt.show()不要提前退出FuncAnimation不播放只显示最后画面interval参数过大或者帧生成器异常检查生成器是否正常yield可以打印len(frames)确认帧数保存 GIF 报错缺少 Pillow执行pip install pillow安装 Pillow 后重试HTML 页面点击开始没有反应浏览器脚本出错或提示语法问题打开开发者工具查看 Console 报错检查是否有复制时遗漏的字符排序结束后仍有乱序柱子冒泡排序边界条件写错将内层循环上限改为n - 1 - i检查j的范围不能越界动画播放速度太慢或太快interval或delay参数不合适调整 Python 的interval和 HTML 的delay建议 Python 用 50 到 100HTML 用 30 到 80柱状图数值显示不完整纵轴范围不够确认ax.set_ylim设置为max(arr) * 1.1增加纵轴缓冲区如果遇到其他问题优先看终端或浏览器控制台里有没有报错信息。把报错信息贴到搜索框里基本能定位到原因。9. 最佳实践与扩展方向9.1 学习算法时怎么用这个可视化建议按下面的顺序学习先看一遍动画理解“最大数字右移”的过程。手写一次冒泡排序伪代码再对照 Python 代码逐行读。修改data为逆序数组、正序数组、含重复数字的数组分别观察动画差异。关闭提前结束机制对比有swapped和没有swapped的动画帧数差距。自己尝试把颜色高亮逻辑改掉比如高亮每一轮的最小值位置。9.2 改造成其他排序算法这个可视化框架不仅适用于冒泡排序。只要把生成器函数bubble_sort_steps里的排序逻辑换成选择排序、插入排序、快速排序等动画框架和渲染函数都可以复用。推荐先改选择排序因为它每轮只需要一次交换动画特征和冒泡排序差异非常明显。学习时可以做一张对比表排序算法每轮行为视觉特征冒泡排序相邻元素反复交换最大元素逐步右移选择排序找最小值放到左侧左侧有序段逐步增长插入排序当前元素向左插入左侧被插入元素局部移动快速排序分区递归整体呈分治结构9.3 增加统计信息和交互控制在教学场景中可以在动画上叠加更丰富的信息当前比较次数当前交换次数本轮是否发生过交换剩余轮次当前正在比较的两个具体数值Python 版可以加ax.text在图表上方显示这些统计量HTML 版可以直接更新p标签内容。想进一步增强交互可以在 HTML 版添加“暂停”“单步”“重置”按钮用flag控制for循环执行。也可以在 JS 中记录帧序列然后点击按钮逐帧播放实现单步调试效果。9.4 课件演示建议录制 GIF 时建议固定分辨率 1280x720控制总时长在 20 秒内。用逆序数组演示最坏情况用接近有序数组演示提前结束优化。强调每一轮结束后右侧的“有序区”这一部分是冒泡排序性能提升的关键。如果做视频素材建议让动画放慢一遍、再快速回放一遍观众更容易建立直觉。10. 总结与下一步这个项目最值得动手的地方是它把一段只有十几行的冒泡排序代码变成了能直观观察的动画演示。先跑通 Python 版验证 24 个数字从乱序到升序的完整过程再打开 HTML 版比较两者效果差异整个流程 10 分钟以内就能完成。最容易踩的坑有三个第一是 Python 代码里因为缺少 Pillow 导致无法保存 GIF第二是 HTML 代码里忘记把内层循环上限改成n - 1 - i造成索引越界第三是不理解生成器的yield机制导致动画只有一帧。前两个按上文的排查表格处理即可。后续可以继续做的方向包括把同样的可视化代码改造成选择排序和快速排序、加入音频反馈、把 HTML 版加上暂停和单步调试、用 Vue 或 React 组件化封装成一个排序算法教学小工具。如果你也正在做算法可视化相关的内容建议先收藏这套代码改参数时能省下不少时间。