Rust: مجموعات Rust الشائعة: VecDeque وBTreeMap ومقارنة الأداء
آخر تحديث: 2026-08-26
VecDeque هي قائمة انتظار ذات طرفين تدعم عمليات الإدراج والحذف بكفاءة من كلا الطرفين؛ أما BTreeMap فهي خريطة مرتبة للمفاتيح والقيم تدعم استعلامات النطاق. وهما عنصران «متخصصان» في مكتبة المجموعات بلغة Rust.
إذا كان Vec يشبه «الانتظار في طابور لركوب الحافلة» (لا يمكنك الانضمام إلا في نهاية الطابور)، فإن VecDeque يشبه «شخصًا يتخطى الطابور» (يمكنك الدخول والخروج من أي طرف). إذا كانت HashMap تشبه «القاموس» (عمليات بحث عشوائية)، فإن BTreeMap تشبه «قائمة جهات الاتصال» (مرتبة أبجديًا، ويمكنك الانتقال إلى صفحة معينة لبدء القراءة).
1. ما ستتعلمه
- VecDeque:
push_front/pop_backالعمليات على قائمة الانتظار ذات الطرفين - BTreeMap: التخزين المرتب للقيم والمفاتيح واستعلامات النطاق
range - حالات الاستخدام والقيود المتعلقة بالقوائم المرتبطة
- مقارنة أداء أربعة أنواع من مجموعات البيانات (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، لكن كل من «التخطي في الطابور» و«مغادرة الطابور» عملية بطيئة
يعمل توم (توم) على تطوير نظام لإدارة قوائم الانتظار للمطاعم. يحصل العملاء على رقم عند وصولهم، ثم ينادي النادل الرقم ليقوم بتوجيههم إلى طاولاتهم.
في البداية، قام بتنفيذها باستخدام 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) — بعد إزالة العنصر الأول، يجب إزاحة جميع العناصر المتبقية إلى الأمام بمقدار موضع واحد. إذا كان هناك 10,000 شخص في الطابور، ففي كل مرة يتم فيها نداء رقم ما، يجب إزاحة 9,999 عنصرًا. ومن الواضح أن هذا غير مناسب لنظام إنتاجي.
(2) متطلبات أكثر تعقيدًا: أولوية كبار الشخصيات + البحث حسب الرقم
ومما زاد الطين بلة، قال مدير المطعم:
- يمكن للعملاء من فئة VIP تجاوز الطابور والانتقال إلى مقدمة الطابور
- يتعين على موظفي خدمة العملاء البحث بسرعة عن معلومات العملاء حسب رقم التذكرة
- في بعض الأحيان يكون من الضروري البحث عن «العملاء الذين أخذوا رقمًا بين الساعة 2:00 مساءً و3:00 مساءً».
لا تستطيع «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) مقارنة بين أربع هياكل للمجموعات
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) مقارنة أداء أربعة أنواع من المجموعات
| العملية | 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 | المجموعة الوحيدة التي تدعم استعلامات النطاق |
| لا تتطلب سوى عمليات «tail» | 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
تُظهر بيانات الأداء أن HashMap هي الأسرع في عمليات البحث (O(1))، تليها 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 في لغة Rust؟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 عمليات تليها عمليتا تراجع، واطبع محتوى كل عملية. - الصعوبة ⭐⭐: قم بتنفيذ «نظام إدارة درجات الطلاب» باستخدام خريطة شجرة B. أدخل أسماء ودرجات 5 طلاب، واطبع الترتيب التنازلي حسب الدرجة (تلميح: لا يمكن لخريطة شجرة B الفرز مباشرةً حسب القيمة؛ يجب عليك استخدام الدرجة كمفتاح والاسم كقيمة، أو استخدام
iter().rev()). - الصعوبة ⭐⭐⭐: اكتب دالة
fn analyze_collections(data: &[i32])تتلقى شريحة من الأعداد الصحيحة كمدخلات، وتحسب عدد مرات تكرار كل رقم باستخدام VecDeque وHashMap وBTreeMap على التوالي، وتقارن الفروق في الأداء بين الطرق الثلاث (باستخدامstd::time::Instantلقياس الوقت).