本文深入讲解Rust中两种经典排序算法——冒泡排序与快速排序的实现原理与代码实践。通过完整的可运行示例、性能对比表格以及分阶段学习路径,帮助读者掌握如何在Rust语言中安全高效地实现基础算法,并理解其背后的时间复杂度与内存使用特性。


一、引言:为什么要在Rust中实现排序算法?

尽管Rust标准库提供了高效的 sort()sort_unstable() 方法(基于优化的内省排序),但在学习阶段手动实现经典排序算法仍然是理解编程语言特性和算法思想的重要方式。

本案例将带你从零开始,在内存安全零成本抽象的Rust哲学下,亲手实现:

  • 冒泡排序(Bubble Sort)——最直观但效率较低
  • 快速排序(Quick Sort)——经典分治法代表,平均性能优异

我们将重点关注:

  • 如何利用Rust的引用与可变借用(&mut)操作数组
  • 避免越界访问的安全实践
  • 使用泛型支持多种数据类型
  • 借助 PartialOrd trait 实现通用比较逻辑

二、代码演示

1. 冒泡排序(Bubble Sort)

/// 冒泡排序:重复遍历数组,相邻元素比较并交换
fn bubble_sort<T: PartialOrd>(arr: &mut [T]) {
    let len = arr.len();
    for i in 0..len {
        // 优化:记录是否发生交换
        let mut swapped = false;
        for j in 0..(len - i - 1) {
            if arr[j] > arr[j + 1] {
                arr.swap(j, j + 1); // 安全交换,避免手动解引用
                swapped = true;
            }
        }
        // 如果没有发生交换,说明已有序
        if !swapped {
            break;
        }
    }
}

2. 快速排序(Quick Sort)

/// 快速排序主函数(递归入口)
fn quick_sort<T: PartialOrd + Clone>(arr: &mut [T]) {
    if arr.len() <= 1 {
        return;
    }
    _quick_sort(arr, 0, (arr.len() - 1) as isize);
}

/// 实际递归实现
fn _quick_sort<T: PartialOrd + Clone>(arr: &mut [T], low: isize, high: isize) {
    if low < high {
        let pivot_index = partition(arr, low, high);
        _quick_sort(arr, low, pivot_index - 1);
        _quick_sort(arr, pivot_index + 1, high);
    }
}

/// 分区函数:Lomuto分区方案
fn partition<T: PartialOrd + Clone>(arr: &mut [T], low: isize, high: isize) -> isize {
    let high_usize = high as usize;
    let pivot = arr[high_usize].clone(); // 选择最后一个元素为基准
    let mut i = low - 1; // 小于基准的区域边界

    for j in low..high {
        let j_usize = j as usize;
        if arr[j_usize] <= pivot {
            i += 1;
            let i_usize = i as usize;
            arr.swap(i_usize, j_usize);
        }
    }

    i += 1;
    let i_usize = i as usize;
    arr.swap(i_usize, high_usize);
    i
}

3. 主函数测试示例

fn main() {
    // 测试整数数组
    let mut numbers = vec![64, 34, 25, 12, 22, 11, 90];
    println!("原始数组: {:?}", numbers);

    bubble_sort(&mut numbers);
    println!("冒泡排序后: {:?}", numbers);

    // 重置数组
    let mut numbers_quick = vec![64, 34, 25, 12, 22, 11, 90];
    quick_sort(&mut numbers_quick);
    println!("快速排序后: {:?}", numbers_quick);

    // 测试字符串
    let mut words = vec!["banana", "apple", "cherry", "date"];
    quick_sort(&mut words);
    println!("字符串排序后: {:?}", words);
}

输出结果:

原始数组: [64, 34, 25, 12, 22, 11, 90]
冒泡排序后: [11, 12, 22, 25, 34, 64, 90]
快速排序后: [11, 12, 22, 25, 34, 64, 90]
字符串排序后: ["apple", "banana", "cherry", "date"]

三、核心知识点解析与关键字高亮

以下是在上述代码中涉及的关键Rust概念及其作用:

关键字/语法 中文解释 在本案例中的用途
&mut [T] 可变切片引用 允许函数修改传入的数组片段,不获取所有权
T: PartialOrd 泛型约束:支持部分顺序比较 使排序函数适用于所有可比较类型的集合
arr.swap(i, j) 安全交换两个索引处的值 替代手动临时变量交换,更安全且语义清晰
Clone 克隆 trait 在快排中复制 pivot 值,避免生命周期问题
isize 有符号整型 支持负数索引计算(防止 underflow)
vec![] 向量宏 创建动态数组(Vec),是 Rust 最常用容器

🔍 关键点详解:

✅ 使用 &mut [T] 而非 Vec<T> 作为参数

我们传递的是切片引用而不是整个 Vec<T>,这样可以:

  • 避免所有权转移
  • 支持对任意子数组进行排序(如 &mut arr[1..5]
  • 提升通用性与性能
PartialOrd vs Ord
  • PartialOrd:表示“部分可比较”,浮点数 NaN 也满足此 trait。
  • Ord:全序关系,更强的约束。

我们使用 PartialOrd 是为了兼容更多类型(包括 f32, f64),虽然需注意浮点数排序时 NaN 的行为。

✅ 为什么快排需要 Clone

因为在分区时我们需要保存 pivot 的副本:

let pivot = arr[high_usize].clone();

如果不克隆,尝试持有引用会导致借用冲突(因为后续 swap 会修改同一 slice)。这是Rust内存安全机制的体现——编译器阻止潜在的数据竞争。


四、数据表格:算法性能对比分析

特性 冒泡排序(Bubble Sort) 快速排序(Quick Sort)
时间复杂度(最好) O(n) O(n log n)
时间复杂度(平均) O(n²) O(n log n)
时间复杂度(最坏) O(n²) O(n²)(极端情况)
空间复杂度 O(1) O(log n)(递归栈)
稳定性 稳定 不稳定(默认实现)
是否原地排序
适用场景 小规模或教学演示 大多数实际应用
Rust实现难度 ⭐☆☆☆☆(简单) ⭐⭐⭐☆☆(中等)
是否推荐生产使用 ❌ 否 ✅ 是(优化版)

💡 注:标准库的 slice::sort() 平均性能优于手写快排,因其采用 introsort(混合算法)


五、分阶段学习路径

为了循序渐进掌握排序算法在Rust中的实现,建议按以下四个阶段进行学习:

📌 第一阶段:理解基础语法与切片操作(1~2小时)

目标:能看懂并运行最简单的冒泡排序

✅ 学习内容:

  • let mut arr = vec![1,2,3];
  • &mut arr[..] 与切片语法
  • for i in 0..n 循环结构
  • if arr[i] > arr[i+1] { arr.swap(i, i+1); }

🎯 练习任务:
编写一个只对 [i32; 5] 类型工作的冒泡排序函数,不使用泛型。


📌 第二阶段:掌握泛型与Trait约束(2~3小时)

目标:写出通用的排序函数,支持 i32Stringf64

✅ 学习内容:

  • 泛型语法 <T>
  • Trait bounds:T: PartialOrd
  • 派生宏:#[derive(PartialEq, PartialOrd)] 对自定义结构体排序

🎯 示例扩展:

#[derive(Debug, PartialEq, PartialOrd)]
struct Student {
    name: String,
    score: f64,
}

// 修改排序逻辑以支持结构体
let mut students = vec![
    Student { name: "Alice".into(), score: 88.5 },
    Student { name: "Bob".into(), score: 92.0 },
];
quick_sort(&mut students); // 按分数升序排列

📌 第三阶段:深入算法细节与性能调优(3~5小时)

目标:理解快排分区机制,尝试不同优化策略

✅ 优化方向:

  • 随机化 pivot:避免最坏情况(已排序输入)
  • 三数取中法:选首、中、尾三者的中位数作为 pivot
  • 小数组切换插入排序:当长度 < 10 时改用插入排序

🎯 进阶挑战:
实现 Hoare 分区版本(比 Lomuto 更高效):

fn partition_hoare<T: PartialOrd>(arr: &mut [T], low: usize, high: usize) -> usize {
    let mut i = low;
    let mut j = high;
    let pivot = arr[low];

    loop {
        while arr[i] < pivot { i += 1; }
        while arr[j] > pivot { j -= 1; }
        if i >= j { return j; }
        arr.swap(i, j);
    }
}

📌 第四阶段:集成测试与性能基准(2~3小时)

目标:验证正确性并测量性能差异

✅ 使用 cargo test 编写单元测试:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bubble_sort_empty() {
        let mut v: Vec<i32> = vec![];
        bubble_sort(&mut v);
        assert_eq!(v, []);
    }

    #[test]
    fn test_quick_sort_sorted() {
        let mut v = vec![1, 2, 3, 4];
        quick_sort(&mut v);
        assert_eq!(v, [1, 2, 3, 4]);
    }

    #[test]
    fn test_reverse_order() {
        let mut v = vec![5, 4, 3, 2, 1];
        quick_sort(&mut v);
        assert_eq!(v, [1, 2, 3, 4, 5]);
    }
}

✅ 使用 criterion crate 做基准测试(Cargo.toml 添加依赖):

[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "sorting_benchmark"
harness = false

创建 benches/sorting_benchmark.rs

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use your_crate::{bubble_sort, quick_sort};

fn benchmark_sorting(c: &mut Criterion) {
    let mut group = c.benchmark_group("Sorting Algorithms");
    
    for size in &[100, 1000] {
        group.bench_with_input(format!("Bubble Sort {}", size), size, |b, &size| {
            let mut data: Vec<i32> = (0..size).rev().collect(); // 逆序数据
            b.iter(|| {
                let mut sorted = data.clone();
                bubble_sort(black_box(&mut sorted));
            })
        });

        group.bench_with_input(format!("Quick Sort {}", size), size, |b, &size| {
            let mut data: Vec<i32> = (0..size).rev().collect();
            b.iter(|| {
                let mut sorted = data.clone();
                quick_sort(black_box(&mut sorted));
            })
        });
    }
    group.finish();
}

criterion_group!(benches, benchmark_sorting);
criterion_main!(benches);

运行命令:

cargo bench

你会看到类似输出:

Sorting Algorithms/Bubble Sort 100 time:   [ ... ] ms
Sorting Algorithms/Quick Sort 100 time:    [ ... ] ms
Sorting Algorithms/Bubble Sort 1000 time:  [ ~50ms ]
Sorting Algorithms/Quick Sort 1000 time:   [ ~0.3ms ]

六、常见问题与陷阱(FAQ)

问题 原因 解决方案
编译错误:“cannot borrow arr as mutable more than once” 多重可变借用冲突 使用 .swap() 或确保作用域分离
数组越界 panic 访问 arr[j+1] 未检查边界 使用 j < len - 1 限制循环范围
浮点数包含 NaN 导致排序失败 NaN != NaN,违反全序 预处理过滤或使用 ordered-float crate
快排栈溢出(Stack Overflow) 递归太深(最坏情况 O(n) 层) 改为迭代+栈模拟,或随机 pivot
泛型无法比较结构体 未实现 PartialOrd trait 添加 #[derive(PartialOrd)] 或手动实现

七、章节总结

在本案例 排序算法实现(冒泡排序、快速排序) 中,我们完成了以下目标:

掌握了Rust中安全操作数组的方法
通过 &mut [T] 切片和 swap() 方法,实现了无需指针运算的安全排序。

理解了泛型与Trait在算法设计中的关键作用
使用 T: PartialOrd 让排序函数具备高度通用性,支持任意可比较类型。

实现了两种经典排序算法并进行了性能对比

  • 冒泡排序适合教学,易于理解;
  • 快速排序具有优秀平均性能,是实践中常用的高级算法模板。

建立了完整的测试与性能评估体系
从单元测试到基准测试,形成闭环验证流程,符合工程级开发规范。

识别了常见错误模式并提供解决方案
包括借用冲突、越界访问、递归深度等问题的规避策略。


八、延伸思考与下一步建议

虽然我们实现了基本版本的排序算法,但在真实项目中仍应优先使用标准库方法:

arr.sort();           // 稳定排序(introsort)
arr.sort_unstable();  // 更快但不稳定
arr.sort_by_key(|x| x.score); // 自定义排序键

不过,手动实现这些算法的价值在于:

  • 加深对 内存模型所有权系统 的理解
  • 提升解决复杂问题的 逻辑建模能力
  • 为后续学习 数据结构(如堆排序、红黑树)打下基础

📌 下一步推荐学习:

  • 归并排序(Merge Sort)——稳定 O(n log n)
  • 堆排序(Heap Sort)——结合二叉堆结构
  • 使用 BinaryHeap 标准库实现优先队列

附录:完整源码(可直接复制运行)

// main.rs
fn bubble_sort<T: PartialOrd>(arr: &mut [T]) {
    let len = arr.len();
    for i in 0..len {
        let mut swapped = false;
        for j in 0..(len - i - 1) {
            if arr[j] > arr[j + 1] {
                arr.swap(j, j + 1);
                swapped = true;
            }
        }
        if !swapped {
            break;
        }
    }
}

fn quick_sort<T: PartialOrd + Clone>(arr: &mut [T]) {
    if arr.len() <= 1 {
        return;
    }
    _quick_sort(arr, 0, (arr.len() - 1) as isize);
}

fn _quick_sort<T: PartialOrd + Clone>(arr: &mut [T], low: isize, high: isize) {
    if low < high {
        let pivot_index = partition(arr, low, high);
        _quick_sort(arr, low, pivot_index - 1);
        _quick_sort(arr, pivot_index + 1, high);
    }
}

fn partition<T: PartialOrd + Clone>(arr: &mut [T], low: isize, high: isize) -> isize {
    let high_usize = high as usize;
    let pivot = arr[high_usize].clone();
    let mut i = low - 1;

    for j in low..high {
        let j_usize = j as usize;
        if arr[j_usize] <= pivot {
            i += 1;
            let i_usize = i as usize;
            arr.swap(i_usize, j_usize);
        }
    }

    i += 1;
    let i_usize = i as usize;
    arr.swap(i_usize, high_usize);
    i
}

fn main() {
    let mut nums = vec![64, 34, 25, 12, 22, 11, 90];
    println!("原始数组: {:?}", nums);

    bubble_sort(&mut nums);
    println!("冒泡排序后: {:?}", nums);

    let mut nums2 = vec![64, 34, 25, 12, 22, 11, 90];
    quick_sort(&mut nums2);
    println!("快速排序后: {:?}", nums2);
}

📌 编译运行:

rustc main.rs && ./main

🔚 结语:排序虽小,五脏俱全。它是通往算法世界的大门,也是检验编程语言掌握程度的一面镜子。通过本案例的学习,你不仅学会了如何在Rust中实现排序,更重要的是体会到了这门语言在安全性、性能与表达力之间的精妙平衡。继续前进吧,前方还有更多激动人心的实战等着你!

Logo

开放原子旋武开源社区(简称“旋武社区”)是由开放原子开源基金会孵化及运营的技术社区,致力于在中国推广和发展Rust编程语言生态,推动Rust在操作系统、终端设备、安全技术、基础软件等关键领域的产业落地,构建安全、可靠、高效的软件基础设施。

更多推荐