十大排序算法性能深度对比

十大排序算法性能深度对比

算法详解
排序性能分析

排序是计算机科学中最基础、最重要的算法问题之一。无论是数据库索引的构建、搜索引擎结果的呈现,还是操作系统进程的调度,排序算法都扮演着不可或缺的角色。Donald Knuth 在其经典著作《计算机程序设计艺术》中曾指出,计算机约有 25% 的运行时间花费在排序上。深入理解各种排序算法的原理、性能特征和适用场景,是每一位程序员的必修课。

本文将对十种经典排序算法进行全面而深入的对比分析,涵盖原理讲解、复杂度分析、代码实现以及实际应用场景。

一、冒泡排序(Bubble Sort)

原理简述: 冒泡排序反复遍历待排序序列,每次比较相邻的两个元素,若顺序错误则交换。每一轮遍历会将当前未排序部分的最大元素"冒泡"到正确位置。

复杂度分析:

  • 最好时间复杂度:O(n)(序列已有序,仅遍历一次)
  • 最坏时间复杂度:O(n²)(序列完全逆序)
  • 平均时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定性:稳定
Python
def bubble_sort(arr):    n = len(arr)    for i in range(n):        swapped = False        for j in range(0, n - 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

二、选择排序(Selection Sort)

原理简述: 选择排序每次从未排序部分中找到最小(或最大)元素,将其放到已排序部分的末尾。算法维护两个子序列:已排序部分和未排序部分。

复杂度分析:

  • 最好时间复杂度:O(n²)
  • 最坏时间复杂度:O(n²)
  • 平均时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定性:不稳定(交换可能打破相同元素的相对顺序)
Python
def selection_sort(arr):    n = len(arr)    for i in range(n):        min_idx = i        for j in range(i + 1, n):            if arr[j] < arr[min_idx]:                min_idx = j        arr[i], arr[min_idx] = arr[min_idx], arr[i]    return arr

三、插入排序(Insertion Sort)

原理简述: 插入排序将数组分为已排序和未排序两部分,每次取未排序部分的第一个元素,在已排序部分中找到合适的位置插入。类似于我们打牌时整理手牌的过程。

复杂度分析:

  • 最好时间复杂度:O(n)(序列已有序)
  • 最坏时间复杂度:O(n²)(序列完全逆序)
  • 平均时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定性:稳定
Python
def insertion_sort(arr):    for i in range(1, len(arr)):        key = arr[i]        j = i - 1        while j >= 0 and arr[j] > key:            arr[j + 1] = arr[j]            j -= 1        arr[j + 1] = key    return arr

插入排序在小规模数据和近乎有序的数据上表现极为优秀,许多高级排序算法(如 Timsort)在处理小规模子数组时会回退到插入排序。

四、希尔排序(Shell Sort)

原理简述: 希尔排序是插入排序的改进版本,核心思想是通过设定一个递减的间隔序列(gap sequence),先对间隔较大的元素进行排序,逐步缩小间隔,最终在间隔为 1 时退化为标准插入排序。由于前期的大间隔排序已使数据趋于有序,最终的插入排序效率大大提高。

复杂度分析:

  • 最好时间复杂度:O(n log n)(取决于间隔序列)
  • 最坏时间复杂度:O(n²)(使用原始 Shell 间隔序列)至 O(n^(4/3))(使用 Sedgewick 序列)
  • 平均时间复杂度:取决于间隔序列,通常介于 O(n log n) 和 O(n²) 之间
  • 空间复杂度:O(1)
  • 稳定性:不稳定
Python
def shell_sort(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]                j -= gap            arr[j] = temp        gap //= 2    return arr

五、快速排序(Quick Sort)

原理简述: 快速排序采用分治策略,选取一个基准元素(pivot),将数组划分为两部分——小于基准的元素和大于基准的元素,然后对两部分递归排序。快速排序的核心在于分区操作(partition)。

复杂度分析:

  • 最好时间复杂度:O(n log n)(每次恰好均匀划分)
  • 最坏时间复杂度:O(n²)(每次取到最值作为基准,如已排序数组取首元素)
  • 平均时间复杂度:O(n log n)
  • 空间复杂度:O(log n)(递归栈深度)
  • 稳定性:不稳定
Python
def quick_sort(arr, low=0, high=None):    if high is None:        high = len(arr) - 1    if low < high:        pivot_idx = partition(arr, low, high)        quick_sort(arr, low, pivot_idx - 1)        quick_sort(arr, pivot_idx + 1, high)    return arr def partition(arr, low, high):    pivot = arr[high]  # 选择最后一个元素作为基准    i = low - 1    for j in range(low, high):        if arr[j] <= pivot:            i += 1            arr[i], arr[j] = arr[j], arr[i]    arr[i + 1], arr[high] = arr[high], arr[i + 1]    return i + 1

快速排序是实践中最常用的排序算法之一,其平均性能极为优秀。通过随机化选取 pivot 或三数取中法(median-of-three),可以有效避免最坏情况。

六、归并排序(Merge Sort)

原理简述: 归并排序同样采用分治策略,将数组递归地分成两半,分别排序后再合并(merge)。合并操作是归并排序的核心,它将两个有序子数组合并为一个有序数组。

复杂度分析:

  • 最好时间复杂度:O(n log n)
  • 最坏时间复杂度:O(n log n)
  • 平均时间复杂度:O(n log n)
  • 空间复杂度:O(n)(需要额外数组存放合并结果)
  • 稳定性:稳定
Python
def merge_sort(arr):    if len(arr) <= 1:        return arr    mid = len(arr) // 2    left = merge_sort(arr[:mid])    right = merge_sort(arr[mid:])    return merge(left, right) def merge(left, right):    result = []    i = j = 0    while i < len(left) and j < len(right):        if left[i] <= right[j]:  # <= 保证稳定性            result.append(left[i])            i += 1        else:            result.append(right[j])            j += 1    result.extend(left[i:])    result.extend(right[j:])    return result

归并排序性能稳定,不受输入数据分布影响,是外部排序(数据量超出内存时)的首选算法。

七、堆排序(Heap Sort)

原理简述: 堆排序利用二叉堆(通常是最大堆)这种数据结构。首先将数组构建为最大堆,然后反复将堆顶(最大值)与末尾元素交换,缩小堆的范围并重新调整堆结构,从而实现排序。

复杂度分析:

  • 最好时间复杂度:O(n log n)
  • 最坏时间复杂度:O(n log n)
  • 平均时间复杂度:O(n log n)
  • 空间复杂度:O(1)
  • 稳定性:不稳定
Python
def heap_sort(arr):    n = len(arr)     # 构建最大堆    for i in range(n // 2 - 1, -1, -1):        heapify(arr, n, i)     # 逐个提取堆顶元素    for i in range(n - 1, 0, -1):        arr[0], arr[i] = arr[i], arr[0]        heapify(arr, i, 0)    return arr def heapify(arr, n, i):    largest = i    left = 2 * i + 1    right = 2 * i + 2    if left < n and arr[left] > arr[largest]:        largest = left    if right < n and arr[right] > arr[largest]:        largest = right    if largest != i:        arr[i], arr[largest] = arr[largest], arr[i]        heapify(arr, n, largest)

堆排序的优势在于最坏情况下仍保证 O(n log n) 且只需 O(1) 额外空间,但由于缓存不友好(跳跃式内存访问),实际运行速度通常不如快速排序。

八、计数排序(Counting Sort)

原理简述: 计数排序是一种非比较排序算法。它通过统计每个元素出现的次数,然后根据统计信息将元素放到正确位置。适用于数据范围有限的整数排序。

复杂度分析:

  • 时间复杂度:O(n + k),其中 k 为数据范围
  • 空间复杂度:O(n + k)
  • 稳定性:稳定
Python
def counting_sort(arr):    if not arr:        return arr    max_val = max(arr)    min_val = min(arr)    range_val = max_val - min_val + 1     count = [0] * range_val    output = [0] * len(arr)     for num in arr:        count[num - min_val] += 1     # 累加计数    for i in range(1, range_val):        count[i] += count[i - 1]     # 从后向前遍历以保持稳定性    for i in range(len(arr) - 1, -1, -1):        output[count[arr[i] - min_val] - 1] = arr[i]        count[arr[i] - min_val] -= 1     return output

九、桶排序(Bucket Sort)

原理简述: 桶排序将数据分配到若干个"桶"中,每个桶内部独立排序(通常使用插入排序或其他排序算法),最后将所有桶的结果拼接起来。当数据均匀分布时,桶排序效率极高。

复杂度分析:

  • 最好时间复杂度:O(n + k)(数据均匀分布,k 为桶的数量)
  • 最坏时间复杂度:O(n²)(所有数据落入同一个桶)
  • 平均时间复杂度:O(n + n²/k + k),当 k ≈ n 时为 O(n)
  • 空间复杂度:O(n + k)
  • 稳定性:稳定(取决于桶内排序算法)
Python
def bucket_sort(arr, bucket_count=10):    if not arr:        return arr    min_val, max_val = min(arr), max(arr)    bucket_range = (max_val - min_val) / bucket_count + 1     buckets = [[] for _ in range(bucket_count)]     for num in arr:        idx = int((num - min_val) / bucket_range)        buckets[idx].append(num)     result = []    for bucket in buckets:        result.extend(insertion_sort(bucket))  # 桶内使用插入排序    return result

十、基数排序(Radix Sort)

原理简述: 基数排序按照位数从低位到高位(LSD)或从高位到低位(MSD)依次进行排序,每一位的排序使用稳定的排序算法(通常是计数排序)。适用于整数或等长字符串排序。

复杂度分析:

  • 时间复杂度:O(d × (n + k)),d 为最大位数,k 为基数(如十进制 k=10)
  • 空间复杂度:O(n + k)
  • 稳定性:稳定
Python
def radix_sort(arr):    if not arr:        return arr    max_val = max(arr)    exp = 1    while max_val // exp > 0:        counting_sort_by_digit(arr, exp)        exp *= 10    return arr def counting_sort_by_digit(arr, exp):    n = len(arr)    output = [0] * n    count = [0] * 10     for num in arr:        idx = (num // exp) % 10        count[idx] += 1     for i in range(1, 10):        count[i] += count[i - 1]     for i in range(n - 1, -1, -1):        idx = (arr[i] // exp) % 10        output[count[idx] - 1] = arr[i]        count[idx] -= 1     for i in range(n):        arr[i] = output[i]

完整对比表格

排序算法最好时间平均时间最坏时间空间复杂度稳定性
冒泡排序O(n)O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
插入排序O(n)O(n²)O(n²)O(1)稳定
希尔排序O(n log n)O(n^1.3)O(n²)O(1)不稳定
快速排序O(n log n)O(n log n)O(n²)O(log n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(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(n+k)稳定
桶排序O(n+k)O(n+k)O(n²)O(n+k)稳定
基数排序O(d(n+k))O(d(n+k))O(d(n+k))O(n+k)稳定

注:k 为数据范围或桶数量,d 为最大位数。

实际应用场景分析

小规模数据(n < 50): 插入排序是最佳选择。常数因子小,且对近乎有序的数据极为高效。

中等规模通用排序: 快速排序在大多数场景中是最快的比较排序算法,平均情况下的常数因子很小。

需要稳定排序: 归并排序是稳定且性能可预测的 O(n log n) 算法。在需要保持相等元素原始顺序的场景中(如多关键字排序),稳定性至关重要。

内存受限: 堆排序只需 O(1) 额外空间,适合内存紧张的嵌入式系统。

数据范围有限的整数排序: 计数排序或基数排序可以实现线性时间复杂度,远超比较排序的 O(n log n) 理论下界。

大规模外部排序: 归并排序天然适合外部排序,因为其顺序访问模式适合磁盘 I/O。

数据均匀分布的浮点数排序: 桶排序可以实现接近线性的排序效率。

各语言标准库排序的底层实现

Python — Timsort

Python 的内置 sort()sorted() 使用 Timsort 算法,由 Tim Peters 于 2002 年设计。Timsort 是一种混合排序算法,结合了归并排序和插入排序的优点:

  • 首先识别数据中已有序的子序列(称为 "run")
  • 对于短的 run,使用插入排序扩展到最小长度(通常为 32 或 64)
  • 然后使用归并排序合并这些 run
  • 通过巧妙的合并策略(galloping mode)优化合并效率

Timsort 的时间复杂度为 O(n log n),在已部分有序的数据上表现尤为出色,最好情况可达 O(n)。

C — qsort

C 标准库的 qsort() 通常基于快速排序实现,但具体实现因编译器而异。glibc 的实现采用了归并排序(当有足够内存时)或快速排序(内存不足时),并在小数组上使用插入排序优化。MSVC 的实现使用内省排序(Introsort),结合快速排序、堆排序和插入排序。

Java — Dual-Pivot Quicksort 与 Timsort

Java 对基本类型数组使用 Dual-Pivot Quicksort(双基准快速排序,由 Vladimir Yaroslavskiy 设计),选取两个基准将数组分为三部分,在实践中比经典快速排序更快。对于对象数组,Java 使用 Timsort,以保证排序的稳定性。

C++ — Introsort

C++ STL 的 std::sort 通常使用 Introsort(内省排序),由 David Musser 于 1997 年提出。它以快速排序开始,当递归深度超过 2×log₂(n) 时切换到堆排序以保证 O(n log n) 的最坏情况,对于小数组则使用插入排序。

Go — Pattern-Defeating Quicksort (pdqsort)

Go 语言标准库采用 pdqsort,这是一种现代化的混合排序算法,在随机数据上接近快速排序的性能,同时能高效处理已排序、逆序、大量重复等特殊模式的数据。

总结与选择建议

排序算法没有绝对的"最好",只有最适合特定场景的选择。以下是简要的决策指南:

  1. 不确定时,使用语言标准库:现代语言标准库的排序实现经过精心优化,是大多数场景的最佳选择。
  2. 追求通用性能:快速排序及其变体(Introsort、pdqsort)是最佳选择。
  3. 需要稳定排序:选择归并排序或 Timsort。
  4. 内存极度受限:选择堆排序(O(1) 空间、O(n log n) 时间)。
  5. 数据特征明确
    • 数据范围小 → 计数排序
    • 整数且位数有限 → 基数排序
    • 均匀分布 → 桶排序
    • 近乎有序 → 插入排序或 Timsort
  6. 教学与面试:冒泡排序和选择排序适合理解排序的基本概念,但在生产环境中几乎不使用。

理解这些算法不仅有助于选择合适的排序方案,更能培养分析问题、设计算法的思维能力。比较排序的理论下界是 O(n log n),而非比较排序在特定条件下可以突破这一限制——这一思想本身就是算法设计中"打破常规约束"的精妙范例。

十大排序算法性能深度对比

https://cot.wiki/blog/sorting-algorithms-comparison

作者Perimsx
发布时间
更新时间
许可协议CC BY-NC-SA 4.0
评论功能集成中