一般的なRustのコレクション:VecDeque、BTreeMap、およびパフォーマンス比較
VecDeque は、両端での効率的な挿入と削除をサポートする両端キューです。BTreeMap は、範囲クエリをサポートする順序付きキーバリューマップです。これらは、Rust のコレクションライブラリに含まれる「特化型」の要素です。
Vecが「バスに乗るために列に並んでいる状態」(列の最後尾にしか加われない)だとすれば、VecDequeは「列に割り込む人」(どちらの端からも出入りできる)のようなものです。HashMapが「辞書」(ランダムな検索)のようなものだとすれば、BTreeMapは「連絡先リスト」(アルファベット順に並べられており、特定のページを開いて読み始めることができる)のようなものです。
1. 学習内容
- VecDeque:
push_front/pop_back両端キューに対する操作 - BTreeMap:順序付きキー・バリュー型ストレージと範囲クエリ
range - リンクドリストのユースケースと制限事項
- 4種類のコレクション(Vec / VecDeque / HashMap / BTreeMap)のパフォーマンス比較
- 選択戦略の設定:シナリオに基づいて適切なデータ構造を選択する
2. 概念図
以下のMermaidによる比較図は、VecDequeという両端キューと、BTreeMapというB木マップのデータ構造の違いを示しています。
graph LR
subgraph VecDeque["VecDeque Double-ended queue"]
direction LR
V1["pop_front O(1)"] --> V2["Element1"]
V2 --> V3["Element2"]
V3 --> V4["..."]
V4 --> V5["ElementN"]
V5 --> V6["push_back O(1)"]
V7["push_front O(1)"] -.-> V2
end
subgraph BTreeMap["BTreeMap BOrdered Map of Trees"]
direction TB
B0["Root Node"] --> B1["Left Branch<br/>Keys: A-M"]
B0 --> B2["Right Branch<br/>Keys: N-Z"]
B1 --> B3["(Alice, 95)"]
B1 --> B4["(Bob, 72)"]
B2 --> B5["(Charlie, 87)"]
B2 --> B6["(David, 91)"]
end
VecDeque -.-> |Comparison of Data Structures| BTreeMap
3. 待ち行列・チケットシステムの物語
(1) 問題:Vec を使ってキューをシミュレートしているが、列に割り込む操作とキューから離れる操作の両方が遅い
トム(Tom)は、レストラン向けの待ち行列管理システムを開発しています。客は来店時に整理券を受け取り、店員がその番号を呼び出して席に案内します。
当初、彼はVecを使ってこれを実装しました:
let mut queue = Vec::new();
queue.push("Customer A"); // A Get a ticket,At the back of the line
queue.push("Customer B"); // B Get a ticket,At the back of the line
queue.push("Customer C"); // C Get a ticket,At the back of the line
// Take a seat when your number is called:Remove from the front of the line
let first = queue.remove(0); // O(n) —— All elements that follow must be moved forward!
println!("{} Take a seat", first);
Vecの
remove(0)はO(n)の操作です。最初の要素を削除した後、残りのすべての要素を1つずつ前方へずらす必要があります。列に10,000人が並んでいる場合、番号が呼ばれるたびに9,999個の要素をずらす必要があります。これは明らかに本番環境には適していません。
(2) より複雑な要件:VIP優先 + 番号による検索
さらに悪いことに、そのレストランのマネージャーはこう言った:
- VIPのお客様は、列の先頭に割り込むことができます
- カスタマーサービス担当者は、チケット番号に基づいて顧客情報を素早く検索する必要があります
- 「午後2時から午後3時の間に整理券を受け取ったお客様」を検索する必要がある場合があります。
Vecは、こうした要件にはまったく対応できません。
(3) Rustのコレクションに関する解決策
use std::collections::VecDeque;
use std::collections::BTreeMap;
fn main() {
// VecDeque:Accessible from both the front and the back
let mut queue = VecDeque::new();
queue.push_back("Customer A"); // Enter at the back of the line
queue.push_back("Customer B");
queue.push_front("VIP"); // VIP Cutting in line at the front of the line!
println!("Next, please take a seat.: {}", queue.pop_front().unwrap()); // VIP
println!("Next: {}", queue.pop_front().unwrap()); // Customer A
// BTreeMap:Ordered Key-Value Store
let mut customers = BTreeMap::new();
customers.insert(1001, "Alice");
customers.insert(1003, "Bob");
customers.insert(1002, "Charlie");
// Automatically Sort and Output Buttons
for (id, name) in &customers {
println!("Get a ticket #{}: {}", id, name);
}
// Output: #1001: Alice, #1002: Charlie, #1003: Bob
}
VecDequeの両端での操作は O(1) ですが、BTreeMap はキーを自動的にソートし、範囲検索をサポートしています。前者は「キューイング」の問題を解決し、後者は「順序付き検索」の問題を解決します。
4. 基本概念
(1) 4つの集合構造の比較
graph TB
A[Rust Collection Library] --> B[Vec: Contiguous Arrays]
A --> C[VecDeque: Double-ended queue]
A --> D[HashMap: Hash Table]
A --> E[BTreeMap: B Ordered Map of Trees]
B --> F["push / pop: O(1) Amortized"]
B --> G["insert(0) / remove(0): O(n)"]
C --> H["push_front / pop_front: O(1)"]
C --> I["push_back / pop_back: O(1)"]
D --> J["Insert / Search: O(1) Amortized"]
D --> K["Disorder —— No order guarantee"]
E --> L["Insert / Search: O(log n)"]
E --> M["Orderly —— Support for Range Queries range"]
(2) 4種類のセットの性能比較
| 操作 | Vec | VecDeque | HashMap | BTreeMap |
|---|---|---|---|---|
| 末尾挿入 | O(1)(平均) | O(1) | O(1)(平均) | O(log n) |
| 先頭への挿入 | O(n) | O(1) | — | — |
| インデックスによるアクセス | O(1) | O(1) | — | — |
| キー検索 | — | — | 平均 O(1) | O(log n) |
| 範囲クエリ | 未対応 | 未対応 | 未対応 | O(log n + k) |
| 順序付き探索 | ソート済みである必要がある | ソート済みである必要がある | 順序なし | O(n) (ソート済み) |
| メモリ使用量 | 低 | 低 | 中 | 中 |
(3) 選定戦略
| シナリオ | おすすめのコレクション | 理由 |
|---|---|---|
| キュー/バッファ(両端での操作) | VecDeque | 両端とも O(1) — これ以上に適したものはない |
| キー順の探索が必要 | BTreeMap | 自動的にソートされるため、追加のソート処理は不要 |
| 範囲クエリが必要 | BTreeMap | 範囲クエリをサポートする唯一のコレクション |
| テール操作のみが必要 | Vec | よりシンプルでメモリ効率が良い |
| キーと値の検索のみが必要 | HashMap | O(1)の検索速度で、BTreeMapよりも高速 |
| 中間の要素の頻繁な挿入・削除 | LinkedList | 理論上は O(1) だが、実際にはほとんど使われない |
(4) VecDeque および BTreeMap の一般的なメソッドのクイックリファレンス
| セット | メソッド | 説明 | 時間計算量 |
|---|---|---|---|
| VecDeque | push_front(val) |
先頭への挿入 | O(1) |
| VecDeque | push_back(val) |
末尾への挿入 | O(1) |
| VecDeque | pop_front() |
配列の先頭要素を取り出す | O(1) |
| VecDeque | pop_back() |
末尾から取り出す | O(1) |
| VecDeque | front() / back() |
先頭要素と末尾要素を表示 | O(1) |
| VecDeque | len() / is_empty() |
長さの問い合わせ | O(1) |
| BTreeMap | insert(k, v) |
キーと値のペアの挿入 | O(log n) |
| BTreeMap | get(&k) |
キーによる検索 | O(log n) |
| BTreeMap | remove(&k) |
キーによる削除 | O(log n) |
| BTreeMap | range(start..=end) |
範囲検索 | O(log n + k) |
| BTreeMap | first_key_value() |
最小のキー・値のペア数 | O(log n) |
| BTreeMap | last_key_value() |
キー・値のペアの最大数 | O(log n) |
5. 模範を示す
(1) ▶ サンプル:VecDeque 両端キュー — キューイングおよびチケット発行システム (難易度 ⭐)
// ============================================
// VecDeque:Basic Operations on a Double-Ended Queue
// ============================================
use std::collections::VecDeque;
fn main() {
let mut queue: VecDeque<&str> = VecDeque::new();
// Regular customers enter from the back of the line
queue.push_back("Alice");
queue.push_back("Bob");
queue.push_back("Charlie");
// VIP A customer cut in line from the front of the line
queue.push_front("VIP-David");
queue.push_front("VIP-Eve");
println!("Current number of people in line: {}", queue.len());
println!("At the head of the line: {:?}, Bringing up the rear: {:?}", queue.front(), queue.back());
// Take a seat when your number is called:Remove from the front of the queue
println!("\nCall Order:");
while let Some(customer) = queue.pop_front() {
println!(" {} Take a seat", customer);
}
}
出力:
Current number of people in line: 5
At the head of the line: Some("VIP-Eve"), Bringing up the rear: Some("Charlie")
Call Order:
VIP-Eve Take a seat
VIP-David Take a seat
Alice Take a seat
Bob Take a seat
Charlie Take a seat
push_front()とpush_back()はどちらも O(1) の操作です。front()とback()は、要素を削除することなく、それぞれキューの先頭と末尾への参照を返します。pop_front()は、キューの先頭にある要素を削除して、その要素を返します。
(2) ▶ サンプル:BTreeMap — 順序付きキー・バリュー型データ構造:成績順位(難易度 ⭐⭐)
// ============================================
// BTreeMap:Automatic Button Sorting
// ============================================
use std::collections::BTreeMap;
fn main() {
let mut scores = BTreeMap::new();
scores.insert("Charlie", 87);
scores.insert("Alice", 95);
scores.insert("Bob", 72);
scores.insert("David", 91);
// BTreeMap Button(Alphabetical order)Automatic Sorting
println!("=== Transcript(Sort by Name)===");
for (name, score) in &scores {
println!(" {}: {}", name, score);
}
// Search by Key
println!("\nAlice the results: {}", scores.get("Alice").unwrap());
// Check for the presence of
if scores.contains_key("Eve") {
println!("Eve There are discrepancies in the scores");
} else {
println!("Eve No results yet");
}
// Get the first and last entries
println!("\nFirst: {:?}", scores.first_key_value());
println!("The last one: {:?}", scores.last_key_value());
}
出力:
=== Transcript(Sort by Name)===
Alice: 95
Bob: 72
Charlie: 87
David: 91
Alice the results: 95
Eve No results yet
First: Some(("Alice", 95))
The last one: Some(("David", 91))
BTreeMap は、キーを
Ordの順序で自動的にソートします。文字列のデフォルトのソート順序は辞書順であるため、「Alice」が先頭になり、「David」が最後になります。first_key_value()およびlast_key_value()を使用すると、先頭要素および末尾要素に効率的にアクセスできます。
(3) ▶ サンプル:BTreeMap の範囲クエリ — 日付範囲クエリ (難易度 ⭐⭐)
// ============================================
// BTreeMap Range Query:range Methods
// ============================================
use std::collections::BTreeMap;
fn main() {
// Simulated Order System:Date -> Order Amount
let mut orders = BTreeMap::new();
orders.insert("2026-07-01", 120);
orders.insert("2026-07-03", 85);
orders.insert("2026-07-05", 200);
orders.insert("2026-07-07", 150);
orders.insert("2026-07-10", 95);
// Search orders placed between July 3 and July 7 (inclusive)
println!("=== July 3 ~ July 7 Orders ===");
for (date, amount) in orders.range("2026-07-03"..="2026-07-07") {
println!(" {}: ${}", date, amount);
}
// Search all orders on or after July 5
println!("\n=== July 5 and After Orders ===");
for (date, amount) in orders.range("2026-07-05"..) {
println!(" {}: ${}", date, amount);
}
// Total Amount
let total: i32 = orders.range("2026-07-03"..="2026-07-07")
.map(|(_, amount)| amount)
.sum();
println!("\nJuly 3 ~ July 7 Total Amount: ${}", total);
}
出力:
=== July 3 ~ July 7 Orders ===
2026-07-03: $85
2026-07-05: $200
2026-07-07: $150
=== July 5 and After Orders ===
2026-07-05: $200
2026-07-07: $150
2026-07-10: $95
July 3 ~ July 7 Total Amount: $435
range()は BTreeMap 独自のメソッドです。このメソッドは範囲式(開区間を表す..、閉区間を表す..=、半開区間を表すstart..)を受け取り、イテレータを返します。これは HashMap ではできないことです。
(4) ▶ サンプル:種類のセットのパフォーマンス比較(難易度 ⭐⭐⭐)
// ============================================
// Comparison of the Performance of Four Types of Sets:Insert 10000 element
// ============================================
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::time::Instant;
fn main() {
let n = 10_000;
// Vec Insert at the end
let start = Instant::now();
let mut vec = Vec::new();
for i in 0..n {
vec.push(i);
}
println!("Vec Insert at the end: {:?}", start.elapsed());
// VecDeque Insert at the end
let start = Instant::now();
let mut deque = VecDeque::new();
for i in 0..n {
deque.push_back(i);
}
println!("VecDeque Insert at the end: {:?}", start.elapsed());
// HashMap Insert
let start = Instant::now();
let mut hmap = HashMap::new();
for i in 0..n {
hmap.insert(i, i);
}
println!("HashMap Insert: {:?}", start.elapsed());
// BTreeMap Insert
let start = Instant::now();
let mut bmap = BTreeMap::new();
for i in 0..n {
bmap.insert(i, i);
}
println!("BTreeMap Insert: {:?}", start.elapsed());
// Search for Performance Comparisons
println!("\n--- Search Performance ---");
let start = Instant::now();
let _ = vec.contains(&9999);
println!("Vec Linear Search: {:?}", start.elapsed());
let start = Instant::now();
let _ = hmap.get(&9999);
println!("HashMap Search: {:?}", start.elapsed());
let start = Instant::now();
let _ = bmap.get(&9999);
println!("BTreeMap Search: {:?}", start.elapsed());
}
出力(例。実際の時間は機種によって異なる場合があります):
Vec Insert at the end: 78.2µs
VecDeque Insert at the end: 82.1µs
HashMap Insert: 1.2ms
BTreeMap Insert: 2.8ms
--- Search Performance ---
Vec Linear Search: 42.5µs
HashMap Search: 138ns
BTreeMap Search: 312ns
パフォーマンスデータによると、検索(O(1))ではHashMapが最も高速で、BTreeMap(O(log n))がそれに続き、Vecは線形検索(O(n))において最も低速であることが示されています。挿入に関しては、末尾挿入ではVecとVecDequeが最も高速で、BTreeMapが最も低速です。これは、「万能の解決策など存在しない」ことを裏付けており、コレクションの選択は具体的なユースケースによって異なるということです。
(5) ▶ サンプル:総合演習—タスクスケジューリングシステム(難易度 ⭐⭐⭐)
// ============================================
// Comprehensive Example:VecDeque Queue + BTreeMap Priority
// ============================================
use std::collections::{VecDeque, BTreeMap};
#[derive(Debug, Clone)]
struct Task {
id: u32,
name: String,
priority: u8,
}
fn main() {
let mut queue: VecDeque<Task> = VecDeque::new();
queue.push_back(Task { id: 1, name: "Check email".into(), priority: 3 });
queue.push_back(Task { id: 2, name: "Fix bug #42".into(), priority: 1 });
queue.push_back(Task { id: 3, name: "Write report".into(), priority: 2 });
queue.push_back(Task { id: 4, name: "Urgent deploy".into(), priority: 0 });
println!("=== FIFO Queue Processing ===");
while let Some(task) = queue.pop_front() {
println!("[P{}] #{}: {}", task.priority, task.id, task.name);
}
let mut priority_map: BTreeMap<u8, Vec<String>> = BTreeMap::new();
let tasks = vec![
("Check email", 3u8), ("Fix bug #42", 1), ("Write report", 2),
("Urgent deploy", 0), ("Code review", 1), ("Team meeting", 3),
("Security patch", 0), ("Update docs", 2),
];
for (name, pri) in tasks {
priority_map.entry(pri).or_insert_with(Vec::new).push(name.to_string());
}
println!("\n=== Sort by priority ===");
for (pri, task_list) in &priority_map {
println!("P{}: {:?}", pri, task_list);
}
println!("\n=== High-Priority Tasks (P0-P1) ===");
for (pri, task_list) in priority_map.range(0..=1) {
println!("P{}: {:?}", pri, task_list);
}
let mut history: VecDeque<String> = VecDeque::with_capacity(3);
for i in 0..5 {
history.push_back(format!("task_{}", i));
if history.len() > 3 {
history.pop_front();
}
}
println!("\nLast 3 history entries: {:?}", history);
}
出力:
=== FIFO Queue Processing ===
[P3] #1: Check email
[P1] #2: Fix bug #42
[P2] #3: Write report
[P0] #4: Urgent deploy
=== Sort by priority ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]
P2: ["Write report", "Update docs"]
P3: ["Check email", "Team meeting"]
=== High-Priority Tasks (P0-P1) ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]
Last 3 history entries: ["task_2", "task_3", "task_4"]
VecDequeをFIFOキューとして使用する(
push_back+pop_front)。BTreeMapは項目を優先度ごとにグループ化し、自動的にソートする。range(0..=1)は優先度の高いタスクのみを表示する。固定容量のVecDequeは、「最新のN件」の履歴機能を実装している。
❓ よくある質問
LinkedListがほとんど使われないのですか?LinkedListの各ノードには追加のポインタオーバーヘッドが発生し、キャッシュ効率が悪いからです。range操作においてページネーションをどのように実装していますか?next()とnth()を使用して、カーソルベースのページネーションを実装しています。📖 まとめ
- VecDeque 両方向連結リスト:
push_front/push_back/pop_front/pop_backはすべて O(1) です - BTreeMap 順序付きマップ:キーは自動的にソートされます。
range()範囲クエリに対応しています - LinkedList は Rust ではほとんど使われません。キャッシュ効率が悪く、実際のパフォーマンスも VecDeque より劣るからです。
- HashMap は検索時間が O(1) ですが、順序は保証されません。BTreeMap は検索時間が O(log n) ですが、順序が保証されています。
- セットの選択方法:動作モード(末尾/両端/検索/範囲)に基づいて、適切なセットを選択します。
- VecDeque と BTreeMap は、std::collections の中で過小評価されている隠れた名作です。多くの場面において、これらは Vec や HashMap よりも適しています。
📝 練習問題
- 難易度 ⭐: VecDeque を使用して「元に戻す履歴」を実装してください。
push_backを使用して各操作を記録し、元に戻す際にはpop_backを使用して最新の操作を削除してください。3 つの操作を行った後に 2 回元に戻す操作を行うシミュレーションを行い、各操作の内容を出力してください。 - 難易度 ⭐⭐:B-Treeマップを用いて「生徒の成績管理システム」を実装してください。5人の生徒の名前と成績を登録し、成績の降順で順位を出力してください(ヒント:B-Treeマップでは値を直接ソートすることはできません。成績をキー、名前を値として扱うか、
iter().rev()を使用する必要があります)。 - 難易度 ⭐⭐⭐: 整数のスライスを入力として受け取り、VecDeque、HashMap、BTreeMapをそれぞれ使用して各桁の出現回数をカウントする関数
fn analyze_collections(data: &[i32])を作成し、3つの手法のパフォーマンスの違いを比較してください(時間の測定にはstd::time::Instantを使用してください)。



