排序算法总结

目录

排序算法

排序算法的稳定性

1. 什么是稳定排序

2. 稳定性的重要性

原地排序(In-place Sorting)

1. 什么是原地排序

2. 原地排序的意义

为什么不能只看时间复杂度

冒泡排序

基础概念

基本思想

执行过程

算法特点

实现方式

基础版本

优化版本(提前结束)

时间复杂度分析

空间复杂度分析

优缺点

适用场景

快速排序

基础概念

基本思想

执行过程

算法特点

实现方式

Lomuto 分区(简单、好理解)

Hoare 分区(更高效,工业常用)

工程化技巧

随机化基准

三数取中(median-of-three)

小数组切换插入排序

时间复杂度分析

空间复杂度分析

优缺点

适用场景

归并排序

基础概念

基本思想

执行过程

算法特点

实现方式

递归实现

迭代(自底向上)实现

时间复杂度分析

空间复杂度分析

优缺点

适用场景

插入排序

基础概念

基本思想

执行过程

算法特点

实现方式

时间复杂度分析

空间复杂度分析

优缺点

适用场景

选择排序

基础概念

基本思想

执行过程

核心特点

实现方式

时间复杂度分析

空间复杂度分析

优缺点

适用场景

堆排序

基础概念

基本思想

执行过程

核心特点

实现方式

时间复杂度分析

空间复杂度分析

优缺点

适用场景

希尔排序

基础概念

基本思想

执行过程

核心特点

实现方式

Shell 原始序列

Hibbard 增量序列

Hibbard 增量序列

时间复杂度分析

常见 gap 序列

Shell 原始序列(最简单)

Hibbard 序列

Sedgewick 序列(推荐)

优缺点

适用场景

归并排序

基础概念

基本思想

执行过程

核心特点

实现方式

递归版

迭代版

时间复杂度分析

空间复杂度分析

优缺点

适用场景

桶排序

基础概念

基本思想

执行过程

核心特点

时间复杂度分析

实现方式

空间复杂度分析

适用前提条件

优缺点

适用场景

计数排序

基础概念

基本思想

执行过程

核心特点

k的意义

为什么复杂度里会出现 k

k与n的关系决定算法可行性

工程实践中的 k 控制技巧

实现方式

时间复杂度分析

空间复杂度分析

适用前提条件

优缺点

应用场景

基数排序

基础概念

分类

基本思想

执行过程

LSD

MSD

核心特点

实现方式

LSD+计数排序

MSD

时间复杂度分析

空间复杂度分析

适用前提条件

优缺点

适用场景


​​​​​​​排序算法

排序算法用于将一组无序数据按照指定规则(如升序或降序)重新排列,是数据结构与算法中的基础内容。在分析和选择排序算法时,除了时间复杂度和空间复杂度外,还需要重点关注两个重要特性:

  1. 排序稳定性(Stability)
  2. 是否为原地排序(In-place Sort)

这两个概念在工程实践中具有直接的业务影响。

排序算法的稳定性

1. 什么是稳定排序

如果在排序前,两个或多个相等关键字的元素在排序后仍保持原有的相对顺序,则称该排序算法是稳定的。

示例

排序前(按成绩排序):

A(90), B(85), C(90)

排序后:B(85), A(90), C(90)

若 A 仍然排在 C 前面,则该排序是稳定的;

若变为:B(85), C(90), A(90),则该排序是不稳定的。

2. 稳定性的重要性

稳定排序在以下场景中非常重要:

  • 多关键字排序
  • 先按次要字段排序
  • 再按主要字段排序
  • 业务数据展示
  • 需要保持原始录入顺序
  • 日志、账单、流水等数据处理

例如:

先按时间排序,再按用户分组,如果排序不稳定,时间顺序可能被破坏。

原地排序(In-place Sorting)

1. 什么是原地排序

如果排序算法在执行过程中 只使用常数级额外空间(O(1)),而不依赖额外的辅助数组,则称该算法为原地排序。递归调用栈通常不计入额外空间。

2. 原地排序的意义

原地排序的优势主要体现在:

  • 内存占用低
  • 缓存友好
  • 适合大规模数据排序
  • 更适合嵌入式或内存受限环境

为什么不能只看时间复杂度

在实际工程中:

  • 稳定性影响业务正确性
  • 是否原地影响系统内存消耗
  • 时间复杂度影响性能上限

因此,排序算法的选择往往是多维度权衡的结果。

冒泡排序

基础概念

冒泡排序是一种基于相邻元素比较、通过不断交换让最大(或最小)元素逐步“浮”到一端

的排序算法。

基本思想

冒泡排序的核心思想是:

  1. 从左到右依次比较相邻元素
  2. 若顺序错误则交换
  3. 每一轮都会把当前最大(或最小)元素放到正确位置
  4. 重复上述过程,直到序列有序

执行过程

以数组为例:

[5, 3, 8, 4]

第一轮

比较 5 和 3 → 交换,[3, 5, 8, 4]

比较 5 和 8 → 不交换,[3, 5, 8, 4]

比较 8 和 4 → 交换,[3, 5, 4, 8]

第一轮结束,8 已就位。

第二轮

比较 3 和 5 → 不交换

比较 5 和 4 → 交换,[3, 4, 5, 8]

第三轮

比较 3 和 5 → 不交换

算法特点

特性

描述

排序思想

交换

是否比较排序

是否稳定

稳定

是否原地排序

最好时间复杂度

O(n)

最坏时间复杂度

O(n²)

平均时间复杂度

O(n²)

空间复杂度

O(1)

实现方式

基础版本

def bubble_sort(arr):

    n = len(arr)

    for i in range(n):

        for j in range(0, n - i - 1):

            if arr[j] > arr[j + 1]:

                arr[j], arr[j + 1] = arr[j + 1], arr[j]

优化版本(提前结束)

def bubble_sort_optimized(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

时间复杂度分析

最坏情况(完全逆序)

比较次数:(n-1) + (n-2) + ... + 1 = n(n-1)/2  → O(n²)

最好情况(已排序,带优化)

一轮比较无交换即可结束 →O(n)

空间复杂度分析

仅使用常数级临时变量

空间复杂度:O(1)

优缺点

优点

  1. 实现简单
  2. 稳定排序
  3. 适合教学和演示

缺点

  1. 时间复杂度高
  2. 实际工程中极少使用

适用场景

  1. 排序算法教学
  2. 数据规模极小
  3. 数据几乎有序(带优化版本)

快速排序

基础概念

快速排序是一种基于分治思想(Divide & Conquer),通过选取基准值(Pivot)将序列划分为小于基准/大于基准两部分,再对左右子序列递归排序的高效排序算法。

基本思想

快速排序包含三个关键步骤:

  1. 选择基准(Pivot)

从数组中选取一个元素作为基准值。

  1. 划分(Partition)

将数组重新排列,使:

左侧元素 ≤ pivot

右侧元素 ≥ pivot

  1. 递归排序

对基准左右两部分分别进行快速排序。

执行过程

以数组为例:

[7, 3, 9, 4, 6]

第 1 次划分

选 pivot = 7

重排后:

[3, 4, 6, 7, 9]

此时:

7 已在最终位置

左侧 [3,4,6]

右侧 [9]

第 2 次递归(左区)

pivot = 3:

[3, 4, 6]

3 已就位,继续递归 [4,6]

排序完成

[3, 4, 6, 7, 9]

算法特点

特性

描述

排序思想

分治

是否比较排序

是否稳定

不稳定

是否原地排序

平均时间复杂度

O(n log n)

最坏时间复杂度

O(n²)

最好时间复杂度

O(n log n)

空间复杂度

O(log n)

实现方式

Lomuto 分区(简单、好理解)

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

Hoare 分区(更高效,工业常用)

def partition(arr, low, high):

    pivot = arr[(low + high) // 2]

    i, j = low - 1, high + 1



    while True:

        i += 1

        while arr[i] < pivot:

            i += 1



        j -= 1

        while arr[j] > pivot:

            j -= 1



        if i >= j:

            return j



        arr[i], arr[j] = arr[j], arr[i]

工程化技巧

随机化基准

import random

pivot_index = random.randint(low, high)

避免最坏情况

三数取中(median-of-three)

取 arr[low]、arr[mid]、arr[high] 的中位数

实际工程中非常常用

小数组切换插入排序

当子数组规模 < 16

使用插入排序更快

时间复杂度分析

最好情况

每次正好平分数组,时间复杂度:O(n log n)。

平均情况

每次划分较均匀,递归深度约为 log n,T(n) = 2T(n/2) + O(n)

→ O(n log n)。

最坏情况

每次选择最小或最大元素作为 pivot,退化为链式递归,T(n) = T(n-1) + O(n)→ O(n²)。

空间复杂度分析

原地排序

递归栈空间:

平均:O(log n)

最坏:O(n)

优缺点

优点

  1. 平均性能极优
  2. 原地排序,缓存友好
  3. 工程实现成熟

缺点

  1. 最坏情况性能差
  2. 不稳定

适用场景

  1. 内存排序
  2. 对性能要求极高
  3. 可接受不稳定排序

归并排序

基础概念

归并排序是一种基于分治思想(Divide & Conquer)将序列不断二分拆分,在回溯阶段有序合并的稳定、高效排序算法。

基本思想

归并排序遵循三个步骤(分治策略):

分(Divide)

将数组递归地拆分成两个子数组,直到每个子数组只剩1个元素。

治(Conquer)

单个元素天然有序,无需处理。

合(Merge)

将两个有序子数组合并成一个新的有序数组。

执行过程

以数组为例:

[8, 3, 5, 4, 7, 6, 1, 2]

拆分阶段

[8,3,5,4]     [7,6,1,2]

[8,3] [5,4]    [7,6] [1,2]

[8] [3] [5] [4]  [7] [6] [1] [2]

合并阶段

[8] + [3] → [3,8]

[5] + [4] → [4,5]

[7] + [6] → [6,7]

[1] + [2] → [1,2]

[3,8] + [4,5] → [3,4,5,8]

[6,7] + [1,2] → [1,2,6,7]

最终

[1,2,3,4,5,6,7,8]

算法特点

特性

描述

排序思想

分治

是否比较排序

是否稳定

稳定

是否原地排序

时间复杂度

始终 O(n log n)

空间复杂度

O(n)

实现方式

递归实现

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

迭代(自底向上)实现

将数组视为 n 个长度为 1 的子数组

两两合并成长度为 2 的子数组

再合并为 4、8... 直到完成

常用于工程优化,避免递归栈溢出

def merge_sort_bottom_up(arr):

    n = len(arr)

    temp = [0] * n   # 辅助数组

    size = 1         # 当前子数组长度



    while size < n:

        # 每次合并相邻的两个 size 长度子数组

        for left in range(0, n, 2 * size):

 &nb
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值