Rust专项——其他集合类型详解:BTreeMap、VecDeque、BinaryHeap
在学习了 Vec、HashMap、HashSet 之后,本节介绍 Rust 标准库中的其他重要集合类型:BTreeMap/BTreeSet(有序映射/集合)、VecDeque(双端队列)、BinaryHeap(优先队列/堆),以及它们的适用场景与性能特点。
1. BTreeMap 与 BTreeSet:有序数据结构
1.1 BTreeMap:基于 B 树的有序映射
BTreeMap<K, V> 提供按键排序的键值对存储,所有键都是有序的。
use std::collections::BTreeMap;fn main() {// 创建一个空的 BTreeMaplet 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:有序集合
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 范围查询与迭代器
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 基本操作
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 应用场景
// 队列(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 基本操作
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 默认是最大堆,要实现最小堆,需要:
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 应用场景
// 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 更优:大多数场景
Vec或VecDeque性能更好
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 | 优先队列 |
| 需要中间插入 | VecDeque 或 LinkedList | 根据频率选择 |
6. 性能基准对比
use std::collections::{HashMap, BTreeMap};
use std::time::Instant;fn benchmark_lookup() {let size = 100_000;// HashMaplet 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());// BTreeMaplet 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 需要排序的场景
// ❌ 错误:需要排序但用了 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
// ❌ 错误:默认是最大堆
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. 实战练习
- 实现 LRU 缓存:使用
VecDeque或LinkedList实现最近最少使用缓存。 - 优先级任务队列:使用
BinaryHeap实现任务调度器,高优先级任务先执行。 - 有序分数排名:使用
BTreeMap存储学生成绩,支持按分数范围查询。 - 滑动窗口最大值:使用
VecDeque高效实现滑动窗口最大值问题。
9. 总结
选择原则
- HashMap vs BTreeMap:需要排序或范围查询用
BTreeMap,否则用HashMap - Vec vs VecDeque:只需要后端操作用
Vec,需要双端操作用VecDeque - BinaryHeap:适合优先队列、Top K、任务调度
- LinkedList:很少使用,优先考虑
Vec或VecDeque
性能要点
HashMap查找最快(O(1) 平均)BTreeMap支持有序和范围查询(O(log n))VecDeque双端操作高效(O(1) 摊销)BinaryHeap适合优先级场景(O(log n) 插入/删除)
下一节 将进入结构体与枚举的学习,这是 Rust 自定义类型的核心。
