概述
本文整理常见排序算法,每种算法包含原理说明、逐步示例、时间空间复杂度及 Python、C/C++ 实现。统一示例数组为 $[5, 2, 8, 1, 9, 3]$,目标升序。
| 算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | $O(n^2)$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 稳定 |
| 选择排序 | $O(n^2)$ | $O(n^2)$ | $O(n^2)$ | $O(1)$ | 不稳定 |
| 插入排序 | $O(n^2)$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 稳定 |
| 希尔排序 | $O(n^{1.3})$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 不稳定 |
| 归并排序 | $O(n\log n)$ | $O(n\log n)$ | $O(n\log n)$ | $O(n)$ | 稳定 |
| 快速排序 | $O(n\log n)$ | $O(n^2)$ | $O(n\log n)$ | $O(\log n)$ | 不稳定 |
| 堆排序 | $O(n\log n)$ | $O(n\log n)$ | $O(n\log n)$ | $O(1)$ | 不稳定 |
| 计数排序 | $O(n+k)$ | $O(n+k)$ | $O(n+k)$ | $O(k)$ | 稳定 |
| 桶排序 | $O(n+k)$ | $O(n^2)$ | $O(n+k)$ | $O(n+k)$ | 稳定 |
| 基数排序 | $O(n \cdot k)$ | $O(n \cdot k)$ | $O(n \cdot k)$ | $O(n+k)$ | 稳定 |
其中 $k$ 为数值范围(计数/桶)或位数(基数)。
冒泡排序 (Bubble Sort)
原理
相邻元素两两比较,若前者大于后者则交换,使较大元素逐步“冒泡”到末尾。每轮确定一个最大值的位置。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$:
第 1 轮(比较到倒数第 1 个):
- 5 vs 2 → 交换 → $[2, 5, 8, 1, 9, 3]$
- 5 vs 8 → 不换 → $[2, 5, 8, 1, 9, 3]$
- 8 vs 1 → 交换 → $[2, 5, 1, 8, 9, 3]$
- 8 vs 9 → 不换 → $[2, 5, 1, 8, 9, 3]$
- 9 vs 3 → 交换 → $[2, 5, 1, 8, 3, \mathbf{9}]$(9 已到位)
第 2 轮(从 $[2, 5, 1, 8, 3, 9]$ 开始,比较到倒数第 2 个):
- 2 vs 5 → 不换 → $[2, 5, 1, 8, 3, 9]$
- 5 vs 1 → 交换 → $[2, 1, 5, 8, 3, 9]$
- 5 vs 8 → 不换 → $[2, 1, 5, 8, 3, 9]$
- 8 vs 3 → 交换 → $[2, 1, 5, 3, \mathbf{8}, \mathbf{9}]$
第 3 轮(从 $[2, 1, 5, 3, 8, 9]$ 开始):
- 2 vs 1 → 交换 → $[1, 2, 5, 3, 8, 9]$
- 2 vs 5 → 不换 → $[1, 2, 5, 3, 8, 9]$
- 5 vs 3 → 交换 → $[1, 2, 3, \mathbf{5}, \mathbf{8}, \mathbf{9}]$
第 4 轮:1 vs 2、2 vs 3 均不换,已有序 → $[\mathbf{1}, \mathbf{2}, \mathbf{3}, \mathbf{5}, \mathbf{8}, \mathbf{9}]$,完成。
复杂度
- 时间:最好 $O(n)$(已序),平均/最坏 $O(n^2)$
- 空间:$O(1)$
代码实现
Python:
1 | def bubble_sort(arr): |
C/C++:
1 | void bubble_sort(int arr[], int n) { |
选择排序 (Selection Sort)
原理
每轮在未排序区间选最小元素,与未排序区首元素交换,使有序区间逐步扩大。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$:
第 1 轮:未排序 $[5, 2, 8, 1, 9, 3]$,最小 1,与 5 交换 → $[\mathbf{1}, 2, 8, 5, 9, 3]$
第 2 轮:未排序 $[2, 8, 5, 9, 3]$,最小 2,已在首位 → $[\mathbf{1}, \mathbf{2}, 8, 5, 9, 3]$
第 3 轮:未排序 $[8, 5, 9, 3]$,最小 3,与 8 交换 → $[\mathbf{1}, \mathbf{2}, \mathbf{3}, 5, 9, 8]$
第 4 轮:未排序 $[5, 9, 8]$,最小 5,已在首位 → $[\mathbf{1}, \mathbf{2}, \mathbf{3}, \mathbf{5}, 9, 8]$
第 5 轮:未排序 $[9, 8]$,最小 8,与 9 交换 → $[\mathbf{1}, \mathbf{2}, \mathbf{3}, \mathbf{5}, \mathbf{8}, \mathbf{9}]$,完成。
复杂度
- 时间:$O(n^2)$(始终 $n(n-1)/2$ 次比较)
- 空间:$O(1)$
代码实现
Python:
1 | def selection_sort(arr): |
C/C++:
1 | void selection_sort(int arr[], int n) { |
插入排序 (Insertion Sort)
原理
将数组分为有序区与无序区,每次取无序区首个元素,在有序区中从后往前找插入位置并插入。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$,初始有序区为 $[5]$:
插入 2:5 > 2,5 后移,插入 → $[\mathbf{2}, \mathbf{5}, 8, 1, 9, 3]$
插入 8:5 < 8,直接插在 5 后 → $[\mathbf{2}, \mathbf{5}, \mathbf{8}, 1, 9, 3]$
插入 1:8、5、2 均 > 1,依次后移,1 插入首位 → $[\mathbf{1}, \mathbf{2}, \mathbf{5}, \mathbf{8}, 9, 3]$
插入 9:8 < 9,直接插在 8 后 → $[\mathbf{1}, \mathbf{2}, \mathbf{5}, \mathbf{8}, \mathbf{9}, 3]$
插入 3:9、8、5 > 3,2 < 3,3 插入 2 与 5 之间 → $[\mathbf{1}, \mathbf{2}, \mathbf{3}, \mathbf{5}, \mathbf{8}, \mathbf{9}]$,完成。
复杂度
- 时间:最好 $O(n)$(已序),平均/最坏 $O(n^2)$
- 空间:$O(1)$
代码实现
Python:
1 | def insertion_sort(arr): |
C/C++:
1 | void insertion_sort(int arr[], int n) { |
希尔排序 (Shell Sort)
原理
插入排序的改进:按递减步长 $h$ 分组(如 $n/2, n/4, \ldots, 1$),对每组做插入排序。步长 1 时等价于普通插入排序。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$,$n=6$,步长序列 3, 1:
步长 3:分为 $(5,1), (2,9), (8,3)$
- $(5,1)$ → $(1, 5)$:$[1, 2, 8, 5, 9, 3]$
- $(2,9)$ → $(2, 9)$:$[1, 2, 8, 5, 9, 3]$
- $(8,3)$ → $(3, 8)$:$[1, 2, 3, 5, 9, 8]$
- 结果:$[1, 2, 3, 5, 9, 8]$(注意 9、8 未交换)
步长 1:对 $[1, 2, 3, 5, 9, 8]$ 做插入排序,插入 8 时 9 后移 → $[1, 2, 3, 5, 8, 9]$,完成。
复杂度
- 时间:平均约 $O(n^{1.3})$(依赖步长),最坏 $O(n^2)$,最好 $O(n)$
- 空间:$O(1)$
代码实现
Python:
1 | def shell_sort(arr): |
C/C++:
1 | void shell_sort(int arr[], int n) { |
归并排序 (Merge Sort)
原理
分治:将数组二分,分别递归排序,再将两个有序子数组合并。合并时用双指针比较两个子数组头部,取较小者放入结果。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$:
分解:$[5,2,8] | [1,9,3]$ → $[5,2]|[8] | [1,9]|[3]$ → $[5]|[2]|[8]|[1]|[9]|[3]$
合并:
- $[5],[2]$ → $[2,5]$
- $[2,5],[8]$ → $[2,5,8]$
- $[1],[9]$ → $[1,9]$
- $[1,9],[3]$ → $[1,3,9]$
- $[2,5,8],[1,3,9]$:双指针,1→2→3→5→8→9 → $[1,2,3,5,8,9]$,完成。
复杂度
- 时间:$O(n\log n)$(分治深度 $\log n$,每层合并 $O(n)$)
- 空间:$O(n)$(辅助数组)
代码实现
Python:
1 | def merge(arr, left, mid, right): |
C/C++:
1 | void merge(int arr[], int left, int mid, int right) { |
快速排序 (Quick Sort)
原理
选一个基准(通常取首/尾/中),将小于基准的放左侧,大于的放右侧,再对左右子区间递归排序。
逐步示例(以末位为基准)
数组 $[5, 2, 8, 1, 9, 3]$,基准 = 3:
分区(Lomuto,基准=3):
- j=0: 5>3 不换;j=1: 2≤3 换 → $[2, 5, 8, 1, 9, 3]$
- j=2: 8>3 不换;j=3: 1≤3 换 → $[2, 1, 8, 5, 9, 3]$
- j=4: 9>3 不换;最后 pivot 归位 → $[2, 1, \mathbf{3}, 5, 9, 8]$,3 在索引 2
左子 $[2, 1]$:基准 1,分区 → $[\mathbf{1}, \mathbf{2}]$
右子 $[5, 9, 8]$:基准 8,分区 → $[5, \mathbf{8}, 9]$;$[5]$、$[9]$ 已有序
合并:$[1, 2, 3, 5, 8, 9]$,完成。
复杂度
- 时间:平均 $O(n\log n)$,最坏 $O(n^2)$(基准总是最值),最好 $O(n\log n)$
- 空间:$O(\log n)$(递归栈)
代码实现
Python:
1 | def partition(arr, low, high): |
C/C++:
1 | void swap(int *a, int *b) { |
堆排序 (Heap Sort)
原理
用数组表示大顶堆:父节点 ≥ 子节点。反复取堆顶(最大值)与末尾交换,堆大小减 1,再对根做下沉(heapify),直到堆为空。
逐步示例(简略)
数组 $[5, 2, 8, 1, 9, 3]$ 建大顶堆:
建堆:从最后一个非叶节点起,自底向上做下沉。
- 初始树:5 为根,左子 2、右子 8;2 子 1、9;8 子 3
- i=2 下沉 (8,3):无;i=1 下沉 (2,1,9):9 上浮 → $[5, 9, 8, 1, 2, 3]$
- i=0 下沉 (5,9,8):9 上浮 → $[9, 5, 8, 1, 2, 3]$
排序(每次交换堆顶与堆尾,再对堆区下沉):
- 9↔3 → $[3, 5, 8, 1, 2 | 9]$,下沉 → $[8, 5, 3, 1, 2 | 9]$
- 8↔2 → $[2, 5, 3, 1 | 8, 9]$,下沉 → $[5, 2, 3, 1 | 8, 9]$
- 5↔1 → $[1, 2, 3 | 5, 8, 9]$,下沉 → $[3, 2, 1 | 5, 8, 9]$
- 3↔1 → $[1, 2 | 3, 5, 8, 9]$,下沉 → $[2, 1 | 3, 5, 8, 9]$
- 2↔1 → $[1 | 2, 3, 5, 8, 9]$,完成
复杂度
- 时间:$O(n\log n)$(建堆 $O(n)$,$n$ 次取顶+下沉 $O(\log n)$)
- 空间:$O(1)$
代码实现
Python:
1 | def heapify(arr, n, i): |
C/C++:
1 | void heapify(int arr[], int n, int i) { |
计数排序 (Counting Sort)
原理
适用于整数且范围 $[0, k]$ 较小的情况。统计每个值出现次数,再按值顺序回填到原数组。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$,假设范围 $[0, 9]$:
计数:count = [0,1,1,1,0,1,0,0,1,1](1 出现 1 次,2 出现 1 次,…)
前缀和(或直接累加回填):count = [0,1,2,3,3,4,4,4,5,6]
回填(从后往前保证稳定):
- 3 → 位置 2 → $[?, ?, 3, ?, ?, ?]$
- 9 → 位置 5 → $[?, ?, 3, ?, ?, 9]$
- 1 → 位置 0 → $[1, ?, 3, ?, ?, 9]$
- 8 → 位置 4 → $[1, ?, 3, ?, 8, 9]$
- 2 → 位置 1 → $[1, 2, 3, ?, 8, 9]$
- 5 → 位置 3 → $[1, 2, 3, 5, 8, 9]$,完成。
复杂度
- 时间:$O(n + k)$
- 空间:$O(k)$(计数数组)
代码实现
Python:
1 | def counting_sort(arr, max_val=None): |
C/C++:
1 | void counting_sort(int arr[], int n, int max_val) { |
桶排序 (Bucket Sort)
原理
将值域分成若干桶,把元素按值放入对应桶,对每个桶内部排序(通常用插入排序),再按桶顺序依次输出。
逐步示例
数组 $[5, 2, 8, 1, 9, 3]$,范围 $[0, 10)$,设 5 个桶,每桶区间长度 2:
- 桶 0 [0,2):1
- 桶 1 [2,4):2, 3
- 桶 2 [4,6):5
- 桶 3 [6,8):空
- 桶 4 [8,10):8, 9
各桶内排序后依次输出:1, 2, 3, 5, 8, 9,完成。
复杂度
- 时间:平均 $O(n+k)$,最坏 $O(n^2)$(元素集中到少数桶)
- 空间:$O(n+k)$
代码实现
Python:
1 | def bucket_sort(arr, bucket_count=5): |
C/C++: 需动态数组,实现略繁,思路同 Python。
基数排序 (Radix Sort)
原理
按位排序:把整数看成多位数(个位、十位、百位……),从最低位到最高位依次做稳定排序。每轮只按当前位排序,必须用稳定排序(如计数排序),才能保证之前轮的相对顺序不被破坏。
为何从低位到高位? 因为高位决定数的大小,低位只起“微调”作用。先按低位排好,再按高位排时,同一高位内的数已经按低位有序,整体自然有序。
取某一位的方法:$(x \div 10^{k-1}) \bmod 10$。例如 $exp=1$ 取个位,$exp=10$ 取十位。
逐步示例
数组 $[52, 21, 18, 91, 93, 35]$,最大 2 位,需 2 轮(个位、十位):
第 1 轮(个位,exp=1):按个位数字排序
- 个位:2, 1, 8, 1, 3, 5
- 计数排序后(稳定):$[21, 91, 52, 93, 35, 18]$(个位 1→1→2→3→5→8)
第 2 轮(十位,exp=10):按十位数字排序
- 十位:2, 9, 5, 9, 3, 1
- 计数排序后(稳定):$[18, 21, 35, 52, 91, 93]$(十位 1→2→3→5→9→9)
完成。注意十位同为 9 的 91、93 保持上一轮顺序,靠稳定性保证。
可视作 05, 02, 08, 01, 09, 03,只需 1 轮(个位):
- 个位计数排序 → $[1, 2, 3, 5, 8, 9]$
复杂度
- 时间:$O(n \cdot k)$,$k$ 为最大位数
- 空间:$O(n + k)$
代码实现
Python:
1 | def counting_sort_by_digit(arr, exp): |
C/C++:
1 | void counting_sort_by_digit(int arr[], int n, int exp) { |


