排序算法分类及实现

排序算法分类及实现

一、排序算法分类

可以从多个维度对排序算法进行分类,最常见的是按 时间复杂度空间复杂度稳定性 来划分,但更本质的分类是按 工作原理

1. 按时间复杂度分类

  • O(n²) :冒泡排序、选择排序、插入排序
  • O(n log n) :快速排序、归并排序、堆排序
  • O(n) :计数排序、桶排序、基数排序(线性阶,但有前提条件)

2. 按空间复杂度分类

  • 原地排序(O(1)):冒泡、选择、插入、希尔、堆排序、快速排序(平均递归栈O(log n))
  • 非原地排序(O(n) 或更多):归并排序、计数排序、桶排序、基数排序

3. 按稳定性分类

  • 稳定排序:相等元素的相对顺序在排序后保持不变(冒泡、插入、归并、计数、桶、基数)
  • 不稳定排序:可能改变相等元素的相对顺序(选择、快速、堆、希尔)

4. 按工作原理分类(核心分类)

类别 代表算法 核心思想
比较排序 冒泡、选择、插入、归并、快速、堆 通过比较元素大小决定顺序,理论下限O(n log n)
非比较排序 计数、基数、桶 利用数据本身的特性,不直接比较,可突破O(n log n)下限
交换排序 冒泡、快速 通过交换元素完成排序
插入排序 插入、希尔 构建有序序列,逐个插入
选择排序 简单选择、堆排序 每次选择最小(大)元素放到合适位置
分治排序 快速、归并 将问题分解为子问题,分别解决后合并

二、经典算法实现(Python示例)

1. 冒泡排序

核心思想 :像气泡一样,较大的元素会逐渐"浮"到顶端。
工作过程

  • 从左到右比较相邻元素,如果顺序不对就交换
  • 每一轮将当前未排序部分的最大元素"冒泡"到正确位置
  • 优化:如果某一轮没有发生交换,说明已排序完成

形象比喻

像水中的气泡,轻的气泡(小值)往上浮,重的气泡(大值)往下沉,但算法实际上是把重的气泡(大值)推到右边。

关键点

  • 稳定排序
  • 每次确定一个元素的最终位置
  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

2. 选择排序

核心思想 :不断选择剩余元素中的最小值,放到已排序序列的末尾。
工作过程

  • 将数组分为已排序区和未排序区
  • 在未排序区中找到最小元素
  • 将其与未排序区的第一个元素交换,扩大已排序区

形象比喻

打扑克牌时,你把手里的牌摊开,每次都找出最小的那张放到左边,直到全部有序。

关键点

  • 不稳定(因为交换可能破坏相等元素的相对顺序)
  • 无论数据如何,比较次数固定
  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

3. 插入排序

核心思想 :像整理扑克牌一样,将每个新元素插入到已排序序列的适当位置。
工作过程

  • 将数组分为已排序区和未排序区
  • 从未排序区取第一个元素
  • 在已排序区中从后向前扫描,找到合适位置插入

形象比喻

打扑克牌时,你一张张摸牌,每摸到一张新牌,就把它插入到手中已排序牌堆的合适位置。

关键点

  • 稳定排序
  • 对部分有序数据效率很高(接近O(n))
  1. 插入排序 - 稳定
python 复制代码
def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

4. 快速排序

核心思想 :分而治之,选择一个"基准"元素,将数组分成两部分:小于基准和大于基准。
工作过程

  1. 选择基准元素(pivot)
  2. 分区操作:将所有小于基准的元素移到左边,大于基准的移到右边
  3. 递归地对左右两个子数组进行快速排序

形象比喻

像整理书籍时,你随便选一本书作为参考,把比它薄的书放左边,比它厚的放右边,然后对左右两边重复这个过程。

关键点

  • 不稳定排序
  • 平均性能最好(O(n log n))
  • 最坏情况(已排序数组)是O(n²)
  1. 快速排序 - 不稳定(递归实现)
python 复制代码
def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    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)

5. 归并排序

核心思想 :分而治之,先将数组不断拆分成最小单元,然后两两合并有序序列。
工作过程

  1. 递归地将数组分成两半,直到每个子数组只有一个元素
  2. 合并两个有序子数组,形成新的有序数组
  3. 重复合并直到整个数组有序

形象比喻

合并两叠已经排好序的卡片:你比较两叠最上面的卡片,总是取较小的那张放到结果堆,直到所有卡片合并。

关键点

  • 稳定排序
  • 需要额外存储空间(O(n))
  • 时间复杂度稳定为O(n log n)
  1. 归并排序 - 稳定
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

6. 堆排序

核心思想 :利用堆(一种完全二叉树)数据结构进行排序。
工作过程

  1. 建立最大堆(父节点值 ≥ 子节点值)
  2. 堆顶元素(最大值)与最后一个元素交换
  3. 缩小堆范围,重新调整堆
  4. 重复直到堆大小为1

形象比喻

像锦标赛:所有选手先进行预赛(建堆),冠军(最大值)确定后,让他退役(放到数组末尾),然后从剩下的选手中再决出冠军。

关键点

  • 不稳定排序
  • 原地排序(O(1)额外空间)
  • 时间复杂度稳定为O(n log n)
  1. 堆排序 - 不稳定
python 复制代码
def heapify(arr, n, i):
    largest = i
    l = 2 * i + 1
    r = 2 * i + 2
    if l < n and arr[l] > arr[largest]:
        largest = l
    if r < n and arr[r] > arr[largest]:
        largest = r
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]
        heapify(arr, n, largest)

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[i], arr[0] = arr[0], arr[i]
        heapify(arr, i, 0)
    return arr

7. 计数排序

核心思想 :不是比较元素大小,而是统计每个值出现的次数。
工作过程

  1. 统计每个元素出现的次数
  2. 计算每个元素在排序后数组中的位置
  3. 按统计结果将元素放到正确位置

形象比喻

像投票计数:有10个候选人(值范围),统计每人的得票数,然后按得票数多少排出名次。

关键点

  • 稳定排序(通过反向填充实现)
  • 只适用于整数且值范围不大的情况
  • 时间复杂度O(n+k),k是值范围
  1. 计数排序 - 稳定(非比较,适合整数小范围)
python 复制代码
def counting_sort(arr):
    if not arr:
        return []
    max_val = max(arr)
    count = [0] * (max_val + 1)
    for num in arr:
        count[num] += 1
    for i in range(1, len(count)):
        count[i] += count[i - 1]
    output = [0] * len(arr)
    for num in reversed(arr):  # 反向遍历保证稳定性
        output[count[num] - 1] = num
        count[num] -= 1
    return output

8. 基数排序

核心思想 :按位排序,从最低位到最高位依次排序。
工作过程

  1. 从最低位开始,使用稳定排序算法(通常用计数排序)按该位排序
  2. 逐步向高位推进
  3. 当最高位排序完成后,整个数组就有序了

形象比喻

整理学生档案时,先按班级(个位)排序,再按年级(十位)排序,最后按学院(百位)排序。

关键点

  • 稳定排序
  • 适用于整数、字符串等可分解为键的数据
  • 时间复杂度O(nk),k是最大位数

LSD(最低位优先)基数排序 - 适用于整数

python 复制代码
def radix_sort(arr):
    """
    基数排序(LSD - 最低位优先)
    适用于正整数,负数需要额外处理
    """
    if not arr:
        return arr
    
    # 找到最大数确定最大位数
    max_num = max(arr)
    
    # 从个位开始,按每一位进行计数排序
    exp = 1  # 1, 10, 100, ...
    
    while max_num // exp > 0:
        # 使用计数排序对当前位进行排序
        counting_sort_by_digit(arr, exp)
        exp *= 10
    
    return arr

def counting_sort_by_digit(arr, exp):
    """
    对数组arr按照某一位(exp决定)进行计数排序
    """
    n = len(arr)
    output = [0] * n  # 输出数组
    count = [0] * 10  # 0-9的数字计数
    
    # 统计当前位上每个数字的出现次数
    for i in range(n):
        digit = (arr[i] // exp) % 10
        count[digit] += 1
    
    # 将count[i]更新为实际位置
    for i in range(1, 10):
        count[i] += count[i - 1]
    
    # 反向填充保证稳定性
    for i in range(n - 1, -1, -1):
        digit = (arr[i] // exp) % 10
        output[count[digit] - 1] = arr[i]
        count[digit] -= 1
    
    # 将排序结果复制回原数组
    for i in range(n):
        arr[i] = output[i]

# 测试基数排序
arr = [170, 45, 75, 90, 802, 24, 2, 66]
print("基数排序前:", arr)
print("基数排序后:", radix_sort(arr.copy()))

支持负数的基数排序

python 复制代码
def radix_sort_with_negative(arr):
    """
    支持负数的基数排序
    """
    if not arr:
        return arr
    
    # 分离正数和负数
    positives = [x for x in arr if x >= 0]
    negatives = [-x for x in arr if x < 0]  # 转换为正数处理
    
    # 分别排序
    if positives:
        radix_sort(positives)
    if negatives:
        radix_sort(negatives)
        negatives = negatives[::-1]  # 反转,因为负数是反向的
        negatives = [-x for x in negatives]  # 转换回负数
    
    # 合并结果:负数 + 正数
    return negatives + positives

# 测试包含负数的基数排序
arr_with_neg = [-5, -100, 0, 123, -45, 67, -3, 99]
print("包含负数的数组:", arr_with_neg)
print("基数排序后:", radix_sort_with_negative(arr_with_neg.copy()))

9. 桶排序

核心思想 :将数据分到有限数量的桶里,每个桶单独排序,最后合并。
工作过程

  1. 设置固定数量的空桶
  2. 将数据分配到对应的桶中
  3. 对每个非空桶进行排序
  4. 按顺序连接所有桶

形象比喻

邮局分拣信件:先按邮政编码范围分到不同格子里,然后对每个格子里的信件再按详细地址排序。

关键点

  • 稳定排序(取决于桶内使用的排序算法)
  • 适用于数据均匀分布的情况
  • 时间复杂度O(n+k),k是桶的数量

基本桶排序

python 复制代码
def bucket_sort(arr, bucket_size=5):
    """
    桶排序
    bucket_size: 每个桶的大小范围,默认5
    """
    if len(arr) == 0:
        return arr
    
    # 找出最大值和最小值
    min_val = min(arr)
    max_val = max(arr)
    
    # 计算桶的数量
    bucket_count = (max_val - min_val) // bucket_size + 1
    buckets = [[] for _ in range(bucket_count)]
    
    # 将数据分配到各个桶中
    for num in arr:
        # 计算当前元素应该放入哪个桶
        bucket_index = (num - min_val) // bucket_size
        buckets[bucket_index].append(num)
    
    # 对每个桶进行排序(这里使用内置排序,也可以用其他排序算法)
    sorted_arr = []
    for bucket in buckets:
        if bucket:
            # 可以使用任何排序算法,这里用内置的sorted
            bucket.sort()  # 或 sorted(bucket)
            sorted_arr.extend(bucket)
    
    return sorted_arr

# 测试桶排序
arr = [0.42, 0.32, 0.23, 0.52, 0.25, 0.47, 0.51]
print("桶排序前:", arr)
print("桶排序后:", bucket_sort(arr.copy()))

三、算法比较总结表

算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性 适用场景
冒泡排序 O(n²) O(n²) O(1) 稳定 教学、小数据
选择排序 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) 稳定 大数据、需稳定
堆排序 O(n log n) O(n log n) O(1) 不稳定 大数据、内存有限
计数排序 O(n + k) O(n + k) O(n + k) 稳定 整数小范围
基数排序 O(nk) O(nk) O(n + k) 稳定 整数、字符串

  • k 为计数排序中的值域范围或基数排序中的位数
  • 快速排序最坏情况可通过随机化或三数取中法避免

四、如何选择排序算法?

  1. 数据量小:插入排序(简单且对部分有序高效)
  2. 数据随机、要求快:快速排序(平均性能好)
  3. 要求稳定、大数据:归并排序
  4. 内存紧张:堆排序
  5. 已知数据范围小、整数:计数排序
  6. 链表结构:归并排序(适合链表)
  7. 基本有序:插入排序或冒泡排序
相关推荐
2301_764441332 小时前
python实现罗斯勒吸引子(Rössler Attractor)
开发语言·数据结构·python·算法·信息可视化
码农三叔2 小时前
(7-3)自动驾驶中的动态环境路径重规划:实战案例:探险家的行进路线
人工智能·算法·机器学习·机器人·自动驾驶
飞Link2 小时前
【Water】数据增强中的数据标注、数据重构和协同标注
算法·重构·数据挖掘
漫随流水2 小时前
leetcode算法(559.N叉树的最大深度)
数据结构·算法·leetcode·二叉树
池塘的蜗牛3 小时前
NR PDSCH和CSI 正交导频设计
算法
CoovallyAIHub3 小时前
仅192万参数的目标检测模型,Micro-YOLO如何做到目标检测精度与效率兼得
深度学习·算法·计算机视觉
sali-tec3 小时前
C# 基于OpenCv的视觉工作流-章10-中值滤波
图像处理·人工智能·opencv·算法·计算机视觉
爱编程的小吴3 小时前
【力扣练习题】151. 反转字符串中的单词
java·算法·leetcode