Python 常用排序算法详解

0 阅读9分钟

讲解视频:notebooklm.google.com/notebook/c2…_

Python 常用排序算法详解

这篇文章整理了Python中从内置工具面试高频手写算法的所有常用排序方法,每个算法都包含「核心思想、代码实现、过程讲解、复杂度分析」,新手也能看懂,面试/刷题直接用,这是本博客的第一篇文章,希望大家多多包涵


一、Python 内置排序(开发首选)

Python 提供了两个开箱即用的排序工具,底层是 Timsort(结合归并排序+插入排序的优化算法),是实际开发中最高效、最稳定的选择。

1. list.sort() 方法(原地排序)

核心特点
  • 直接修改原列表,不返回新列表
  • 支持升序/降序,支持自定义排序规则
  • 稳定排序(相等元素的相对位置不变)
代码示例
# 1. 基础用法:默认升序
nums = [3, 1, 4, 1, 5, 9, 2, 6]
nums.sort()
print("升序结果:", nums)  # [1, 1, 2, 3, 4, 5, 6, 9]

# 2. 降序排序
nums.sort(reverse=True)
print("降序结果:", nums)  # [9, 6, 5, 4, 3, 2, 1, 1]

# 3. 自定义规则:按字符串长度排序
words = ["banana", "apple", "cherry", "date"]
words.sort(key=lambda x: len(x))  # key指定排序依据
print("按长度排序:", words)  # ['date', 'apple', 'banana', 'cherry']
复杂度分析
  • 时间复杂度:平均 / 最好 / 最坏均为 O (n log n)(Timsort 优化后,几乎不会出现最坏情况)
  • 空间复杂度:O (n)(Timsort 需要额外空间处理合并)
  • 稳定性:稳定

2. sorted () 函数(返回新列表)

核心特点
  • 不修改原列表,返回一个新的排序列表
  • 可对任意可迭代对象排序(列表、元组、字符串、字典等)
  • 用法和 sort () 类似,同样支持 key 和 reverse 参数
代码示例
# 1. 对列表排序(不修改原列表)
nums = [3, 1, 4, 1, 5]
new_nums = sorted(nums)
print("原列表:", nums)      # [3, 1, 4, 1, 5](不变)
print("新列表:", new_nums)  # [1, 1, 3, 4, 5]

# 2. 对字典按值排序
students = {"Alice": 85, "Bob": 78, "Charlie": 92}
# 按分数升序排序,返回元组列表
sorted_students = sorted(students.items(), key=lambda x: x[1])
print("按分数排序:", sorted_students)  # [('Bob', 78), ('Alice', 85), ('Charlie', 92)]
复杂度分析

和 list.sort() 完全一致,仅返回新列表,空间上会多占用一份原列表的空间。

二、基础交换类排序(入门必学)

这类算法是排序的基础,逻辑简单易理解,适合新手入门,也是面试中常考的手写题。

1. 冒泡排序(Bubble Sort)

核心思想

像水里的气泡一样,相邻元素两两比较,大的慢慢 “冒” 到后面,每一轮都会把当前最大的元素放到正确位置。 alt

过程讲解
  • 从第一个元素开始,和下一个元素比较,若前者大于后者则交换
  • 每一轮结束后,当前最大的元素会被移到末尾
  • 重复 n-1 轮(n 为列表长度),直到所有元素有序
  • 优化:如果某一轮没有发生任何交换,说明列表已经有序,可以直接退出
代码实现
def bubble_sort(arr):
    n = len(arr)
    # 外层循环:控制轮数,每轮确定一个最大元素的位置
    for i in range(n):
        swapped = False  # 标记本轮是否发生交换
        # 内层循环:比较相邻元素,注意每轮结束后末尾i个元素已经有序,无需再比较
        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

# 测试
print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))  # [11, 12, 22, 25, 34, 64, 90]
复杂度分析
  • 时间复杂度: 平均情况:O(n²)
  • 空间复杂度:O(1) (原地排序,仅用了几个变量)

2. 选择排序(Selection Sort)

核心思想

每一轮在未排序部分找到最小的元素,放到已排序部分的末尾,就像打扑克时,每次从剩下的牌里挑最小的放到前面。 alt

过程讲解
  • 将列表分为「已排序部分」和「未排序部分」,初始时已排序部分为空
  • 遍历未排序部分,找到最小元素的索引
  • 将最小元素和未排序部分的第一个元素交换,该元素加入已排序部分
  • 重复 n-1 轮,直到所有元素有序
代码实现
def selection_sort(arr):
    n = len(arr)
    # 外层循环:控制已排序部分的末尾位置
    for i in range(n):
        min_idx = i  # 假设当前i位置是未排序部分的最小值
        # 内层循环:遍历未排序部分,找到真正的最小值索引
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        # 将最小值和当前i位置交换
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

# 测试
print(selection_sort([64, 25, 12, 22, 11]))  # [11, 12, 22, 25, 64]
复杂度分析
  • 时间复杂度: 最好 / 最坏 / 平均情况:均为 O(n²)(无论列表是否有序,都要遍历找最小值)
  • 空间复杂度:O(1)(原地排序)
  • 稳定性:不稳定(交换操作可能破坏相等元素的相对位置,例如 [2, 2, 1] 排序后两个 2 的位置会改变)

三、进阶高效排序(面试高频)

这类算法的时间复杂度是 O (n log n),效率远高于基础排序,是面试中手写算法的重点。

1. 快速排序(Quick Sort)

核心思想

分治思想的经典实现:选一个基准元素,将列表分为「比基准小的左半部分」和「比基准大的右半部分」,再递归对左右两部分进行快速排序,直到子列表长度为 1(自然有序)。 alt

过程讲解
  • 选一个基准元素(pivot,通常选第一个、最后一个或中间元素,优化时可随机选)
  • 定义左右指针,左指针从左往右找比 pivot 大的元素,右指针从右往左找比 pivot 小的元素
  • 交换左右指针找到的元素,直到两指针相遇,将基准元素放到相遇位置(此时左边都比 pivot 小,右边都比 pivot 大)
  • 递归处理左半部分和右半部分,直到子列表长度≤1
代码实现
def quick_sort(arr, low=0, high=None):
    # 初始化high为列表末尾索引
    if high is None:
        high = len(arr) - 1
    # 递归终止条件:low >= high,说明子列表长度≤1,已有序
    if low >= high:
        return arr
    
    # 1. 分区操作:将列表分为左右两部分,返回基准元素的最终位置
    pivot_index = partition(arr, low, high)
    # 2. 递归排序左半部分(基准左边)
    quick_sort(arr, low, pivot_index - 1)
    # 3. 递归排序右半部分(基准右边)
    quick_sort(arr, pivot_index + 1, high)
    return arr

def partition(arr, low, high):
    # 优化:随机选基准,避免最坏情况
    import random
    pivot_idx = random.randint(low, high)
    arr[pivot_idx], arr[high] = arr[high], arr[pivot_idx]  # 把基准放到末尾,方便处理
    pivot = arr[high]  # 基准元素
    i = low - 1  # 小于基准的元素的边界索引
    # 遍历low到high-1的元素
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    # 把基准放到正确位置(i+1)
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

# 测试
print(quick_sort([10, 7, 8, 9, 1, 5]))  # [1, 5, 7, 8, 9, 10]

最简快速排序

代码实现

def quick_sort(arr):
    # 递归终止条件:列表长度≤1,直接返回
    if len(arr) <= 1:
        return arr
    # 选择第一个元素作为基准
    pivot = arr[0]
    # 拆分:左=小于基准,中=等于基准,右=大于基准
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    # 递归拼接结果
    return quick_sort(left) + middle + quick_sort(right)

# 测试
print(quick_sort([3, 6, 8, 10, 1, 2, 1]))  # [1, 1, 2, 3, 6, 8, 10]
复杂度分析
  • 时间复杂度: 最好 / 最坏 / 平均情况:均为 O(n log n)(无论列表是否有序,都要分 log n 层,每层合并 n 个元素)
  • 空间复杂度:O(n)(需要额外的辅助列表存储合并结果,递归栈深度为 log n,可忽略)
  • 稳定性:稳定(合并时相等元素会按原顺序放入结果列表,相对位置不变)
  • 适用场景:大数据量、要求稳定排序的场景(如外排序、链表排序),Timsort 底层就用到了归并排序的优化

2.归并排序

核心思想

分治思想的稳定实现:将列表不断二分,直到每个子列表只有一个元素(自然有序),再将有序的子列表两两合并,最终得到完整的有序列表。 alt

过程讲解
  • 分:将列表从中间分为左右两个子列表,递归对左右子列表进行归并排序,直到子列表长度为 1
  • 治(合并):将两个有序的子列表合并成一个新的有序列表 定义两个指针,分别指向左右子列表的开头 比较两个指针指向的元素,将较小的元素放入结果列表,对应指针后移 直到其中一个子列表遍历完,将另一个子列表的剩余元素直接追加到结果列表
代码实现
def merge_sort(arr):
    # 递归终止条件:子列表长度≤1,已有序
    if len(arr) <= 1:
        return arr
    # 1. 分:将列表从中间分为左右两部分
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])  # 递归排序左半部分
    right = merge_sort(arr[mid:])  # 递归排序右半部分
    # 2. 治:合并两个有序列表
    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

# 测试
print(merge_sort([38, 27, 43, 3, 9, 82, 10]))  # [3, 9, 10, 27, 38, 43, 82]

四、排序算法对比总结

算法时间复杂度(平均)时间复杂度(最坏)空间复杂度稳定性适用场景
Timsort(内置)O(n log n)O(n log n)O(n)稳定实际开发、所有场景
冒泡排序O(n²)O(n²)O(1)稳定教学演示、数据量极小的场景
选择排序O(n²)O(n²)O(1)不稳定教学演示、数据量极小的场景
快速排序O(n log n)O(n²)O(log n)不稳定大数据量、内存有限的场景
归并排序O(n log n)O(n log n)O(n)稳定大数据量、要求稳定排序的场景