ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用Python实现11种排序算法可视化:从冒泡到快排的视频生成指南

2026/8/30 4:31:48 拓冰建站 浏览量
用Python实现11种排序算法可视化:从冒泡到快排的视频生成指南 在学习数据结构与算法时排序算法往往是很多人绕不开的一道坎。教材里的伪代码、纸质笔记上的箭头总让人在脑子里想象“交换”究竟是怎么发生的。于是可视化成了最直观的辅助工具。本文会带你用 Python 从零实现一个排序算法可视化程序从冒泡排序到快速排序一次性演示 11 种常见排序算法的全过程并直接生成一个可供反复观看的视频。整篇文章会按照“认知算法 → 搭建环境 → 设计可视化框架 → 逐一实现 11 种排序算法 → 合成视频 → 排错与优化”的顺序展开。无论你是刚学数据结构的新手还是准备面试想快速回顾排序算法的开发者都能跟着文章把代码跑通最终得到属于自己的排序算法演示视频。1. 排序算法可视化演示能带来什么1.1 为什么学习排序算法需要可视化排序算法的本质是对一组数据反复进行比较和移动直到满足有序性。这个“反复”的过程如果只靠文字描述很容易让人混淆。比如冒泡排序每一轮会把最大值“浮”到末尾快速排序会通过基准值把数组切分成两部分归并排序则先把数组拆到最小再逐步合并。这些过程用语言描述总是抽象的但一旦变成动画思路立刻清晰了。可视化演示的核心思路很简单把待排序的数组用柱状图表示每个柱子的高度对应数组元素的值。排序过程中用不同颜色标记当前正在比较、交换或已经就位的元素。观众能够直观看到数据从混乱到有序的每一步变化。这种学习方式比死记硬背算法步骤更牢固也比直接看代码更容易理解算法之间的差异。对面试准备者来说排序算法的可视化同样有价值。面试官经常问的“冒泡排序和快速排序的区别是什么”如果你脑子里有清晰的动画印象回答起来会更有底气。本文介绍的 11 种排序算法基本覆盖了面试和课程中常见的所有类型。1.2 一次覆盖 11 种常见排序算法本文要演示的 11 种常见排序算法是冒泡排序Bubble Sort选择排序Selection Sort插入排序Insertion Sort鸡尾酒排序Cocktail Sort希尔排序Shell Sort归并排序Merge Sort快速排序Quick Sort堆排序Heap Sort计数排序Counting Sort桶排序Bucket Sort基数排序Radix Sort这 11 种算法覆盖了多种时间复杂度和设计思路包含 O(n²) 级别的简单排序、O(n log n) 级别的分治与堆排序以及线性时间复杂度的非比较排序。通过对比它们的执行过程你会更深刻地理解不同排序算法的适用场景。1.3 避免概念混淆冒泡排序与事件冒泡搜索“冒泡排序”时有前端经验的同学可能会同时看到“事件冒泡”这个词。事件冒泡是浏览器中 DOM 事件传播机制的概念指的是当一个元素触发事件后事件会从目标元素逐层向上传播到根节点。如果需要阻止事件继续向上传播前端会调用stopPropagation方法。这和排序算法中的冒泡排序完全不是一个领域的东西只是中文翻译都带有“冒泡”两个字很容易让人误以为它们有关联。在阅读本文时请记住我们讨论的是数据结构和算法中的排序方法不要和前端事件机制混淆。这个区分在面试和学习中非常重要否则很容易在概念题上闹出笑话。2. 11种常见排序算法一览2.1 排序算法的分类排序算法可以按多种方式分类。按是否需要比较元素大小可以分为比较排序和非比较排序。冒泡排序、快速排序、归并排序等都属于比较排序它们通过两两比较来决定元素的相对顺序而计数排序、桶排序、基数排序则利用了数据本身的分布特征不需要进行元素之间的比较因此在特定数据范围内能获得更快的速度。按算法思路分类又可以分为交换排序、选择排序、插入排序、分治排序等。理解这些分类能帮助你面对不同数据规模时选择合适的算法。2.2 时间复杂度与稳定性对比在动手写代码之前先看一张整体对比表。这张表是学习排序算法最核心的参考建议收藏。算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n²)O(n²)O(1)稳定选择排序O(n²)O(n²)O(1)不稳定插入排序O(n²)O(n²)O(1)稳定鸡尾酒排序O(n²)O(n²)O(1)稳定希尔排序取决于增量序列O(n²)O(1)不稳定归并排序O(n log n)O(n log n)O(n)稳定快速排序O(n log n)O(n²)O(log n)不稳定堆排序O(n log n)O(n log n)O(1)不稳定计数排序O(n k)O(n k)O(k)稳定桶排序O(n k)O(n²)O(n)稳定基数排序O(d(n k))O(d(n k))O(n k)稳定其中n 表示数组元素个数k 表示数据范围或桶的数量d 表示最大数字的位数。理解这张表是学习排序算法的第一步。后续的可视化演示会帮助你理解为什么有些算法是稳定的有些不稳定。3. 环境准备与项目结构3.1 Python 环境与依赖库本文的示例代码使用 Python 实现建议使用 Python 3.8 及以上版本。可视化部分依赖 matplotlib数据处理部分会用到 numpy但为了保持代码尽量精简核心演示只用 matplotlib 就可以完成。先安装依赖库pip install matplotlib numpy如果你使用 Anaconda则默认已经安装了 matplotlib 和 numpy可以直接跳过这一步。本文示例使用的环境如下操作系统Windows / macOS / Linux 均可Python3.8matplotlib3.5numpy1.21版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。3.2 FFmpeg 与视频导出如果只是想在本地窗口里观看排序过程不需要额外安装任何工具。但如果想把动画保存成 mp4 视频文件就需要安装 FFmpeg。matplotlib 的FuncAnimation.save()方法在保存视频时会调用 FFmpeg。不同平台的安装方式如下# Ubuntu / Debian sudo apt install ffmpeg # macOS brew install ffmpeg # Windows使用 conda conda install -c conda-forge ffmpeg安装完成后可以在命令行执行ffmpeg -version检查是否安装成功。如果不想安装 FFmpeg也可以把视频保存为 GIF 动图只需要把writer参数改为pillow但 GIF 文件通常比 mp4 大很多且帧率表现不如 mp4 流畅。3.3 项目目录规划为了让代码结构清晰我建议把项目拆成三个文件sorting_visualizer/ ├── visualizer.py # 可视化框架 ├── algorithms.py # 11种排序算法 ├── main.py # 主程序生成视频 └── output/ # 视频输出目录这样的拆分可以让“可视化逻辑”和“排序算法逻辑”彼此独立。后续如果你想增加新的排序算法只需要在algorithms.py里添加一个函数并在算法字典里注册即可不需要改动可视化框架。4. 可视化框架核心实现4.1 数据条与状态记录可视化框架的核心是SortingVisualizer类。它负责三件事接收初始数组、记录排序过程中的每一帧状态、最后播放或保存动画。这里的“帧状态”指的是某个时刻数组的元素值和需要高亮的柱子索引。在初始状态我们把数组渲染成柱状图。每个柱子的默认颜色为蓝色排序中需要重点观察的元素会变成红色。为了让动画记录与排序逻辑解耦我们不在排序函数内部直接操作画布而是让排序函数调用snapshot()方法把当前数组状态和需要高亮的位置记录下来。这种设计的好处是排序算法只需要关心自己的逻辑不需要知道视频是怎么生成的。下面是visualizer.py的完整代码# 文件路径sorting_visualizer/visualizer.py import matplotlib.pyplot as plt import matplotlib.animation as animation class SortingVisualizer: def __init__(self, data, titleSorting Visualization): self.data data.copy() self.n len(data) self.title title self.frames [] def snapshot(self, arr, highlight(), titleNone): 记录一帧状态。 arr当前数组 highlight需要高亮的柱子索引列表 title当前帧的标题默认使用初始化时的标题。 self.frames.append((arr.copy(), tuple(highlight), title or self.title)) def play(self, interval30, save_pathNone, dpi100): fig, ax plt.subplots(figsize(10, 6)) bars ax.bar(range(self.n), self.data, color#4C72B0) ax.set_ylim(0, max(self.data) * 1.1) ax.set_xlim(0, self.n) def update(frame): arr, highlight, title frame for bar, h in zip(bars, arr): bar.set_height(h) for i, bar in enumerate(bars): bar.set_color(#E74C3C if i in highlight else #4C72B0) ax.set_title(title) return bars anim animation.FuncAnimation( fig, update, framesself.frames, intervalinterval, repeatFalse ) if save_path: anim.save(save_path, writerffmpeg, dpidpi) print(f视频已保存{save_path}) else: plt.show() return anim4.2 实时显示与录制动画上面的类同时支持两种使用方式调用play()但不传save_path会在本地弹出一个窗口实时播放排序动画。调用play()并传入save_pathoutput/xxx.mp4会把动画保存成 mp4 视频文件。在update函数中每一帧都会重新设置柱子的高度和颜色。highlight中记录的索引对应的柱子显示为红色其余柱子显示为默认蓝色。为了让动画节奏可控interval参数控制每帧之间的间隔时间单位是毫秒。interval30表示每 30 毫秒刷新一帧整体看起来会比较流畅。4.3 排序算法与框架的对接方式排序算法函数的统一签名是def some_sort(viz, arr): # 排序逻辑 # 每次数组发生变化时调用 viz.snapshot(arr, [要高亮的索引]) passviz是SortingVisualizer的实例arr是待排序的列表。排序算法直接在arr上进行原地修改每次元素位置发生变化后就调用一次snapshot()记录当前状态。这种“算法只记录框架负责播放”的模式让排序算法代码非常干净也方便后续扩展。只要遵守这个约定任何排序算法都可以接入可视化框架。5. 平方级排序算法的可视化5.1 冒泡排序冒泡排序Bubble Sort是最经典的入门排序算法。它的思想是重复遍历数组比较相邻两个元素如果顺序错误就交换。每一轮遍历后最大的元素会像气泡一样“浮”到数组末尾因此得名。下面是冒泡排序的可视化代码放在algorithms.py中# 文件路径sorting_visualizer/algorithms.py def bubble_sort(viz, arr): n len(arr) for i in range(n): swapped False for j in range(n - i - 1): viz.snapshot(arr, [j, j 1]) if arr[j] arr[j 1]: arr[j], arr[j 1] arr[j 1], arr[j] swapped True viz.snapshot(arr, [j, j 1]) if not swapped: break viz.snapshot(arr, [])在这段代码中每次比较前先记录一帧交换后再记录一帧。这样动画里可以看到红色柱子在相邻位置跳跃直观感受“比较”和“交换”两个动作。swapped变量是一个优化如果某一轮遍历没有发生任何交换说明数组已经有序可以提前结束。5.2 选择排序选择排序Selection Sort的思路是每一轮从未排序区间中找到最小元素然后放到已排序区间的末尾。它的特点是交换次数少但比较次数依然是 O(n²)。def selection_sort(viz, arr): n len(arr) for i in range(n): min_idx i for j in range(i 1, n): viz.snapshot(arr, [min_idx, j]) if arr[j] arr[min_idx]: min_idx j arr[i], arr[min_idx] arr[min_idx], arr[i] viz.snapshot(arr, [i, min_idx]) viz.snapshot(arr, [])代码中min_idx记录当前轮次最小元素的位置。每次扫描时高亮最小值和当前扫描位置交换时再次记录一帧。观察动画时你会发现选择排序的“扫描”过程很漫长但“交换”动作很少这是它和冒泡排序最明显的视觉差异。5.3 插入排序插入排序Insertion Sort的思想类似于打扑克牌时整理手牌每次把新元素插入到已经有序的序列中的正确位置。def insertion_sort(viz, arr): for i in range(1, len(arr)): temp arr[i] j i - 1 while j 0 and arr[j] temp: arr[j 1] arr[j] viz.snapshot(arr, [j, j 1]) j - 1 arr[j 1] temp viz.snapshot(arr, [j 1]) viz.snapshot(arr, [])在动画中插入排序的柱状图会呈现“左侧逐渐有序右侧仍为原始顺序”的状态。这种算法对近乎有序的数据效率很高是很多混合排序算法比如 Timsort的重要组成部分。5.4 鸡尾酒排序鸡尾酒排序Cocktail Sort是冒泡排序的改进版本它从两个方向交替遍历数组。先从左到右把最大值送到末尾再从右到左把最小值送到开头因此也叫“定向冒泡排序”或“双向冒泡排序”。def cocktail_sort(viz, arr): n len(arr) start, end 0, n - 1 swapped True while swapped: swapped False for i in range(start, end): viz.snapshot(arr, [i, i 1]) if arr[i] arr[i 1]: arr[i], arr[i 1] arr[i 1], arr[i] swapped True viz.snapshot(arr, [i, i 1]) if not swapped: break end - 1 swapped False for i in range(end - 1, start - 1, -1): viz.snapshot(arr, [i, i 1]) if arr[i] arr[i 1]: arr[i], arr[i 1] arr[i 1], arr[i] swapped True viz.snapshot(arr, [i, i 1]) start 1 viz.snapshot(arr, [])看动画时你会发现红色柱子像波浪一样来回移动每一轮能同时确定一个最大值和一个最小值的位置比普通冒泡排序效率略高。6. 分治与堆排序的可视化6.1 希尔排序希尔排序Shell Sort是插入排序的改进版。它先将数组按一定间隔 gap 分组对每组进行插入排序然后逐步缩小 gap直到 gap 为 1。这样做可以让元素在宏观上先接近有序最后再做一次“基本插入排序”从而显著提升效率。def shell_sort(viz, arr): n len(arr) gap n // 2 while gap 0: for i in range(gap, n): temp arr[i] j i while j gap and arr[j - gap] temp: arr[j] arr[j - gap] viz.snapshot(arr, [j, j - gap]) j - gap arr[j] temp viz.snapshot(arr, [j]) gap // 2 viz.snapshot(arr, [])在希尔排序的动画里你会看到柱子不是像冒泡排序那样逐位移动而是“跳跃式”地移动。这是因为间隔较大时元素一次可以跨越多个位置整体上数组会更早地呈现出有序趋势。6.2 归并排序归并排序Merge Sort采用分治思想先把数组不断拆分成两半直到每个子数组只有一个元素然后再把有序的子数组合并成一个更大的有序数组。它保证了 O(n log n) 的时间复杂度是稳定排序。def merge_sort(viz, arr): def _merge(arr, low, mid, high): left arr[low:mid] right arr[mid:high] i j 0 k low while i len(left) and j len(right): if left[i] right[j]: arr[k] left[i] i 1 else: arr[k] right[j] j 1 viz.snapshot(arr, [k]) k 1 while i len(left): arr[k] left[i] viz.snapshot(arr, [k]) i 1 k 1 while j len(right): arr[k] right[j] viz.snapshot(arr, [k]) j 1 k 1 def _merge_sort(arr, low, high): if high - low 1: return mid (low high) // 2 _merge_sort(arr, low, mid) _merge_sort(arr, mid, high) _merge(arr, low, mid, high) _merge_sort(arr, 0, len(arr)) viz.snapshot(arr, [])看归并排序动画时最大的感触是“分久必合”。前半段数组看起来只是被反复切割后半段合并过程中柱子局部变得越来越有序直到整个数组有序。归并排序虽然需要额外空间但性能稳定非常适合处理大规模数据。6.3 快速排序快速排序Quick Sort是实际应用最广泛的排序算法之一。它选择一个基准值 pivot将数组分成小于基准值和大于基准值的两部分再递归地对两部分继续排序。快速排序的平均时间复杂度是 O(n log n)但最坏情况会退化到 O(n²)。def quick_sort(viz, arr): def partition(low, high): pivot arr[high] i low - 1 for j in range(low, high): viz.snapshot(arr, [j, high]) if arr[j] pivot: i 1 arr[i], arr[j] arr[j], arr[i] viz.snapshot(arr, [i, j]) arr[i 1], arr[high] arr[high], arr[i 1] viz.snapshot(arr, [i 1, high]) return i 1 def _quick_sort(low, high): if low high: pi partition(low, high) _quick_sort(low, pi - 1) _quick_sort(pi 1, high) _quick_sort(0, len(arr) - 1) viz.snapshot(arr, [])在快速排序的动画里你会看到基准值所在的柱子通常被标记为红色其他柱子通过与它比较被分到左右两侧。每次分区后基准值就固定到了最终位置这也是快排“分治”思想的直观体现。6.4 堆排序堆排序Heap Sort利用堆这种数据结构来排序。它先把数组构建成一个大顶堆然后反复把堆顶元素和末尾元素交换缩小堆的范围并对新的堆顶执行下沉操作。def heap_sort(viz, arr): n len(arr) def heapify(size, root): largest root left 2 * root 1 right 2 * root 2 if left size and arr[left] arr[largest]: largest left if right size and arr[right] arr[largest]: largest right if largest ! root: arr[root], arr[largest] arr[largest], arr[root] viz.snapshot(arr, [root, largest]) heapify(size, largest) for i in range(n // 2 - 1, -1, -1): heapify(n, i) for i in range(n - 1, 0, -1): arr[i], arr[0] arr[0], arr[i] viz.snapshot(arr, [0, i]) heapify(i, 0) viz.snapshot(arr, [])堆排序的动画看起来很有节奏感先是建堆阶段大量调整然后是“交换堆顶 → 缩减堆范围 →