Rust专项——其他集合类型详解:BTreeMap、VecDeque、BinaryHeap

在学习了 VecHashMapHashSet 之后,本节介绍 Rust 标准库中的其他重要集合类型:BTreeMap/BTreeSet (有序映射/集合)、VecDeque (双端队列)、BinaryHeap(优先队列/堆),以及它们的适用场景与性能特点。


1. BTreeMap 与 BTreeSet:有序数据结构

1.1 BTreeMap:基于 B 树的有序映射

BTreeMap<K, V> 提供按键排序的键值对存储,所有键都是有序的。

rust 复制代码
use std::collections::BTreeMap;

fn main() {
    // 创建一个空的 BTreeMap
    let mut map = BTreeMap::new();

    // 向 BTreeMap 中插入键值对
    map.insert(3, "three");
    map.insert(1, "one");
    map.insert(2, "two");

    // 1. 演示自动按键排序的遍历输出
    println!("开始遍历 BTreeMap(自动按键排序):");
    for (k, v) in &map {
        println!("键: {}, 值: {}", k, v);
    }
    // 预期输出(因 BTreeMap 会按键升序排列):
    // 键: 1, 值: one
    // 键: 2, 值: two
    // 键: 3, 值: three


    // 2. 演示范围查询(range(2..=3))并输出结果
    println!("\n开始范围查询(键在 2 到 3 之间,包含两端):");
    let range: Vec<_> = map.range(2..=3).collect();
    for (idx, (k, v)) in range.iter().enumerate() {
        println!("第 {} 个匹配项 - 键: {}, 值: {}", idx + 1, k, v);
    }
    // 预期 range 内容(因按序排列,会匹配键 2 和 3):
    // [(2, "two"), (3, "three")]
}

与 HashMap 的对比

特性 HashMap BTreeMap
顺序 无序 有序(按键排序)
查找 O(1) 平均 O(log n)
插入 O(1) 平均 O(log n)
范围查询 ❌ 不支持 ✅ 支持
内存开销 较大(哈希表) 较小(B树)
适用场景 快速查找、无序访问 需要排序、范围查询

1.2 BTreeSet:有序集合

rust 复制代码
use std::collections::BTreeSet;

fn main() {
    // 1. 创建 BTreeSet 并插入元素
    let mut set = BTreeSet::new();
    set.insert(5);
    set.insert(1);
    set.insert(3);

    println!("=== BTreeSet 自动排序遍历 ===");
    // 2. 演示自动排序的遍历输出
    for val in &set {
        println!("遍历值: {}", val);
    }
    // 预期输出顺序:1 → 3 → 5(因 BTreeSet 按升序维护元素)


    println!("\n=== 查找最小值和最大值 ===");
    // 3. 查找并输出最小值
    if let Some(min) = set.first() {
        println!("最小值: {}", min);
    } else {
        println!("集合为空,无最小值");
    }

    // 4. 查找并输出最大值
    if let Some(max) = set.last() {
        println!("最大值: {}", max);
    } else {
        println!("集合为空,无最大值");
    }
}

1.3 范围查询与迭代器

rust 复制代码
use std::collections::BTreeMap;

fn main() {
    // 创建 BTreeMap 存储学生分数(自动按键的字典序排序)
    let mut scores = BTreeMap::new();
    scores.insert("Alice", 95);
    scores.insert("Bob", 87);
    scores.insert("Charlie", 92);
    scores.insert("David", 78);

    // 1. 输出所有学生的分数(验证BTreeMap 会按姓名的字典序排列)
    println!("=== 所有学生分数(按姓名排序) ===");
    for (name, score) in &scores {
        println!("{}: {}分", name, score);
    }


    // 2. 获取并输出所有90分及以上的优秀学生
    let excellent: Vec<_> = scores.iter()
        .filter(|(_, &score)| score >= 90)  // 筛选分数≥90的条目
        .collect();
    
    println!("\n=== 90分及以上的优秀学生 ===");
    for (name, score) in excellent {
        println!("{}: {}分", name, score);
    }


    // 3. 范围查询:获取姓名首字母在 "B" 到 "D" 之间的学生(字典序范围)
    let range: Vec<_> = scores.range("B"..="D").collect();
    
    println!("\n=== 姓名在 B-D 范围内的学生 ===");
    for (name, score) in range {
        println!("{}: {}分", name, score);
    }


    // 4. 获取并输出排序后的第一个和最后一个元素(按姓名字典序)
    println!("\n=== 按姓名排序的首尾元素 ===");
    if let Some((name, score)) = scores.first_key_value() {
        println!("第一个(字典序最小): {} - {}分", name, score);
    }
    if let Some((name, score)) = scores.last_key_value() {
        println!("最后一个(字典序最大): {} - {}分", name, score);
    }
}

2. VecDeque:双端队列

VecDeque<T> 支持在两端高效插入和删除,是实现队列和栈的理想选择。

2.1 基本操作

rust 复制代码
use std::collections::VecDeque;

let mut deque = VecDeque::new();

// 后端操作(类似 Vec)
deque.push_back(1);
deque.push_back(2);
deque.push_back(3);

// 前端操作(Vec 不支持)
deque.push_front(0);

println!("{:?}", deque); // [0, 1, 2, 3]

// 弹出操作
let front = deque.pop_front(); // Some(0)
let back = deque.pop_back();   // Some(3)

// 访问
let first = deque.front();     // Some(&1)
let last = deque.back();       // Some(&2)

2.2 性能特点

  • 两端插入/删除:O(1) 摊销时间
  • 中间插入/删除:O(n)
  • 随机访问:O(1)

2.3 应用场景

rust 复制代码
// 队列(FIFO):使用 push_back + pop_front
fn queue_demo() {
    let mut queue = VecDeque::new();
    queue.push_back("task1");
    queue.push_back("task2");
    
    while let Some(task) = queue.pop_front() {
        println!("处理: {}", task);
    }
}

// 栈(LIFO):使用 push_back + pop_back(或 push_front + pop_front)
fn stack_demo() {
    let mut stack = VecDeque::new();
    stack.push_back("item1");
    stack.push_back("item2");
    
    while let Some(item) = stack.pop_back() {
        println!("弹出: {}", item);
    }
}

// 滑动窗口:双端队列的经典应用
fn sliding_window(nums: &[i32], k: usize) -> Vec<i32> {
    let mut deque = VecDeque::new();
    let mut result = Vec::new();
    
    for (i, &num) in nums.iter().enumerate() {
        // 移除窗口外的元素
        if let Some(&idx) = deque.front() {
            if idx + k <= i {
                deque.pop_front();
            }
        }
        
        // 维护递减队列(用于找最大值)
        while let Some(&idx) = deque.back() {
            if nums[idx] < num {
                deque.pop_back();
            } else {
                break;
            }
        }
        deque.push_back(i);
        
        // 窗口形成后记录最大值
        if i >= k - 1 {
            result.push(nums[deque[0]]);
        }
    }
    result
}

3. BinaryHeap:优先队列/最大堆

BinaryHeap<T> 实现最大堆(Max Heap),根节点始终是最大值。

3.1 基本操作

rust 复制代码
use std::collections::BinaryHeap;

let mut heap = BinaryHeap::new();

// 插入元素
heap.push(3);
heap.push(1);
heap.push(4);
heap.push(1);
heap.push(5);

// 获取最大值(不移除)
println!("最大值: {:?}", heap.peek()); // Some(5)

// 弹出最大值
while let Some(max) = heap.pop() {
    println!("{}", max);
}
// 输出:5, 4, 3, 1, 1(从大到小)

3.2 最小堆实现

BinaryHeap 默认是最大堆,要实现最小堆,需要:

rust 复制代码
use std::collections::BinaryHeap;
use std::cmp::Reverse;

// 方法1:使用 Reverse 包装
let mut min_heap = BinaryHeap::new();
min_heap.push(Reverse(5));
min_heap.push(Reverse(1));
min_heap.push(Reverse(3));

if let Some(Reverse(min)) = min_heap.pop() {
    println!("最小值: {}", min); // 1
}

// 方法2:自定义结构体实现 Ord
#[derive(Debug, Eq, PartialEq)]
struct MinItem(i32);

impl Ord for MinItem {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        other.0.cmp(&self.0) // 反向比较
    }
}

impl PartialOrd for MinItem {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

let mut custom_heap = BinaryHeap::new();
custom_heap.push(MinItem(5));
custom_heap.push(MinItem(1));
if let Some(MinItem(min)) = custom_heap.pop() {
    println!("最小值: {}", min); // 1
}

3.3 应用场景

rust 复制代码
// 1. Top K 问题
fn top_k_largest(nums: Vec<i32>, k: usize) -> Vec<i32> {
    let mut heap = BinaryHeap::new();
    for num in nums {
        heap.push(num);
        if heap.len() > k {
            heap.pop();
        }
    }
    heap.into_sorted_vec().into_iter().rev().collect()
}

// 2. 合并 K 个有序数组
fn merge_k_sorted(arrays: Vec<Vec<i32>>) -> Vec<i32> {
    let mut heap = BinaryHeap::new();
    let mut result = Vec::new();
    
    // 初始化:每个数组的第一个元素
    for (arr_idx, arr) in arrays.iter().enumerate() {
        if !arr.is_empty() {
            heap.push((Reverse(arr[0]), arr_idx, 0));
        }
    }
    
    while let Some((Reverse(val), arr_idx, elem_idx)) = heap.pop() {
        result.push(val);
        let arr = &arrays[arr_idx];
        if elem_idx + 1 < arr.len() {
            heap.push((Reverse(arr[elem_idx + 1]), arr_idx, elem_idx + 1));
        }
    }
    result
}

// 3. 任务调度(按优先级)
#[derive(Debug, PartialEq, Eq)]
struct Task {
    priority: i32,
    name: String,
}

impl Ord for Task {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        other.priority.cmp(&self.priority) // 高优先级先执行
    }
}

impl PartialOrd for Task {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

fn task_scheduler() {
    let mut queue = BinaryHeap::new();
    queue.push(Task { priority: 3, name: "低优先级任务".into() });
    queue.push(Task { priority: 10, name: "高优先级任务".into() });
    queue.push(Task { priority: 5, name: "中优先级任务".into() });
    
    while let Some(task) = queue.pop() {
        println!("执行: {} (优先级: {})", task.name, task.priority);
    }
}

4. LinkedList:链表(不推荐常用)

LinkedList<T> 是双向链表,但在 Rust 中不常用,因为:

  • 缓存不友好:节点分散在内存中
  • 性能较差:即使是简单操作也可能较慢
  • Vec 更优 :大多数场景 VecVecDeque 性能更好
rust 复制代码
use std::collections::LinkedList;

let mut list = LinkedList::new();
list.push_back(1);
list.push_back(2);
list.push_front(0);

// 仅在需要频繁中间插入/删除时考虑使用
// 通常 VecDeque 是更好的选择

5. 集合类型选择指南

5.1 映射类型对比

需求 推荐类型 理由
快速查找,无序 HashMap O(1) 平均时间复杂度
需要按键排序 BTreeMap 有序,支持范围查询
小数据集(< 100项) 任意 性能差异可忽略
需要范围查询 BTreeMap HashMap 不支持

5.2 集合类型对比

需求 推荐类型 理由
快速查找,无序 HashSet O(1) 平均
需要排序,范围查询 BTreeSet 有序集合
需要找最值 BTreeSet first/last 方法
简单去重 HashSet 性能最优

5.3 队列/栈选择

需求 推荐类型 理由
只需要后端操作 Vec 最简单
需要双端操作 VecDeque 两端 O(1)
需要优先级 BinaryHeap 优先队列
需要中间插入 VecDequeLinkedList 根据频率选择

6. 性能基准对比

rust 复制代码
use std::collections::{HashMap, BTreeMap};
use std::time::Instant;

fn benchmark_lookup() {
    let size = 100_000;
    
    // HashMap
    let mut hash_map = HashMap::new();
    for i in 0..size {
        hash_map.insert(i, i * 2);
    }
    let start = Instant::now();
    for i in 0..size {
        let _ = hash_map.get(&i);
    }
    println!("HashMap 查找: {:?}", start.elapsed());
    
    // BTreeMap
    let mut btree_map = BTreeMap::new();
    for i in 0..size {
        btree_map.insert(i, i * 2);
    }
    let start = Instant::now();
    for i in 0..size {
        let _ = btree_map.get(&i);
    }
    println!("BTreeMap 查找: {:?}", start.elapsed());
    // 通常 HashMap 快 2-5 倍
}

7. 常见错误与修复

错误1:误用 HashMap 需要排序的场景

rust 复制代码
// ❌ 错误:需要排序但用了 HashMap
let mut map = HashMap::new();
map.insert("z", 1);
map.insert("a", 2);
for (k, v) in &map {
    println!("{}: {}", k, v); // 顺序不确定
}

// ✅ 修复:使用 BTreeMap
let mut map = BTreeMap::new();
map.insert("z", 1);
map.insert("a", 2);
for (k, v) in &map {
    println!("{}: {}", k, v); // a: 2, z: 1(有序)
}

错误2:BinaryHeap 想实现最小堆但忘记 Reverse

rust 复制代码
// ❌ 错误:默认是最大堆
let mut heap = BinaryHeap::new();
heap.push(5);
heap.push(1);
println!("{}", heap.peek().unwrap()); // 5(最大值)

// ✅ 修复:使用 Reverse
let mut heap = BinaryHeap::new();
heap.push(Reverse(5));
heap.push(Reverse(1));
println!("{}", heap.peek().unwrap().0); // 1(最小值)

8. 实战练习

  1. 实现 LRU 缓存 :使用 VecDequeLinkedList 实现最近最少使用缓存。
  2. 优先级任务队列 :使用 BinaryHeap 实现任务调度器,高优先级任务先执行。
  3. 有序分数排名 :使用 BTreeMap 存储学生成绩,支持按分数范围查询。
  4. 滑动窗口最大值 :使用 VecDeque 高效实现滑动窗口最大值问题。

9. 总结

选择原则

  • HashMap vs BTreeMap :需要排序或范围查询用 BTreeMap,否则用 HashMap
  • Vec vs VecDeque :只需要后端操作用 Vec,需要双端操作用 VecDeque
  • BinaryHeap:适合优先队列、Top K、任务调度
  • LinkedList :很少使用,优先考虑 VecVecDeque

性能要点

  • HashMap 查找最快(O(1) 平均)
  • BTreeMap 支持有序和范围查询(O(log n))
  • VecDeque 双端操作高效(O(1) 摊销)
  • BinaryHeap 适合优先级场景(O(log n) 插入/删除)

下一节 将进入结构体与枚举的学习,这是 Rust 自定义类型的核心。

相关推荐
渡我白衣8 小时前
C++世界的混沌边界:undefined_behavior
java·开发语言·c++·人工智能·深度学习·语言模型
88Ra8 小时前
Spring Boot 3.3新特性全解析
java·spring boot·后端
剑海风云8 小时前
JDK 26:HTTP/3 支持已可在 HTTP 客户端 API 中使用
java·开发语言·http
好学且牛逼的马8 小时前
【SSM框架 | day24 spring IOC 与 DI】
java·后端·spring
朝新_8 小时前
【SpringBoot】配置文件
java·spring boot·笔记·后端·spring·javaee
掘金码甲哥8 小时前
新来的外包,限流算法用的这么6
后端
叹雪飞花8 小时前
借助Github Action实现通过 HTTP 请求触发邮件通知
后端·开源·github
曾经的三心草8 小时前
springCloud二-SkyWalking3-性能剖析-⽇志上传-告警管理-接入飞书
后端·spring·spring cloud
下一站丶8 小时前
【JavaScript性能优化实战】
开发语言·javascript·性能优化