الطابور (Queue)
آخر تحديث
للطابور طرفان نشطان. تنضم القيم الجديدة عند المؤخرة، وتغادر القيم من المقدمة، فتُخدَم أولًا القيمة التي انتظرت أطول مدة. هذا هو FIFO، وهو بالضبط سلوك الصف أمام شباك الخدمة: الانضمام من الخلف والخدمة من الأمام هو ما يجعل الانتظار عادلًا. اضغط على تشغيل في الأعلى وشاهد القيم تدخل من جهة وتخرج من الجهة الأخرى.
لأن كل طرف يُتابَع بفهرس أو مؤشر خاص به، فالعمليتان كلتاهما O(1) ولا تُزيح أي منهما بقية البيانات. لهذا يقف الطابور تحت كل ما يعالج العمل بترتيب الوصول: مهام الطباعة، وطوابير المهام والرسائل، ومخازن الطلبات المؤقتة، و البحث بالعرض الذي يزور الرسم البياني مستوى بعد مستوى لأنه يحفظ جبهته في طابور تحديدًا. انقل طرف الإزالة إلى الخلف وسيصبح لديك المكدس بدلًا منه.
تعقيد الوقت والمساحة
لطابور مبني على مخزن حلقي أو على قائمة مترابطة، وهما التنفيذان المعياريان:
| العملية | التعقيد | ملاحظات |
|---|---|---|
| الإدراج (enqueue) | O(1) | الكتابة عند المؤخرة وتقديم فهرس المؤخرة. |
| الإزالة (dequeue) | O(1) | القراءة عند المقدمة وتقديم فهرس المقدمة، دون أي إزاحة. |
| الاطّلاع على المقدمة (peek) | O(1) | قراءة قيمة المقدمة دون إزالتها. |
| البحث | O(n) | ليس هذا ما وُجد الطابور له: عليك تفريغه لتنظر في داخله. |
| المساحة | O(n) | خانة واحدة لكل قيمة منتظرة. |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | يبدأ الطابور فارغًا، والمقدمة والمؤخرة تشيران إلى الخانة نفسها. |
| 2 | يكتب الإدراج القيمة عند المؤخرة، ثم يقدّم المؤخرة خانة واحدة. |
| 3 | كل إدراج تالٍ يستقر خلف القيم المنتظرة أصلًا. |
| 4 | تقرأ الإزالة القيمة عند المقدمة، ثم تقدّم المقدمة خانة واحدة. |
| 5 | القيمة العائدة هي دائمًا القيمة التي انتظرت أطول مدة. |
| 6 | عندما تلتقي المقدمة بالمؤخرة يعود الطابور فارغًا، وأي إزالة بعد ذلك خطأ. |
مثال محلول
إدراج 3 و7 و5 ثم تفريغ الطابور:
| العملية | الطابور (من المقدمة إلى المؤخرة) | ما يعيده |
|---|---|---|
enqueue(3) | [3] | لا شيء |
enqueue(7) | [3, 7] | لا شيء |
enqueue(5) | [3, 7, 5] | لا شيء |
dequeue() | [7, 5] | 3، أقدم قيمة |
dequeue() | [5] | 7 |
dequeue() | [] | 5، أحدث قيمة، وتأتي أخيرًا |
متى تستخدم الطابور
| استخدمه عندما | تجنّبه عندما |
|---|---|
| يجب معالجة العمل بترتيب الوصول: طوابير المهام، ومخازن الطلبات المؤقتة، ومُخزِّنات الطباعة | تحتاج إلى أحدث عنصر أولًا، وهذا عمل المكدس |
| تستكشف مستوى بعد مستوى، كما يفعل البحث بالعرض | يجب خدمة العناصر بالأولوية لا بترتيب الوصول، وهنا تناسب الكومة |
| يعمل منتج ومستهلك بسرعتين مختلفتين ويحتاجان إلى مخزن مؤقت بينهما | تحتاج إلى البحث أو الفهرسة في منتصف البيانات |
تريد إدراجًا وإزالة بـ O(1) دون إزاحة العناصر | ستنفّذه بإزاحة مصفوفة عند كل إزالة، مما يجعله O(n) |
كود Queue
تنفيذ نظيف وقابل للتشغيل لخوارزمية Queue بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Queue بلغة Python
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7 queue.append(value)8 print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12 value = queue.popleft()13 print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)كود Queue بلغة JavaScript
1// A plain array makes dequeue O(n): shift() moves every element left.2// Track a head index instead, the fix the queue article describes.3const queue = { items: [], head: 0 };4
5function enqueue(value) {6 queue.items.push(value);7}8
9function dequeue() {10 const value = queue.items[queue.head];11 queue.items[queue.head] = undefined; // free the slot12 queue.head += 1;13 // Reclaim space once the consumed prefix dominates.14 if (queue.head * 2 >= queue.items.length) {15 queue.items = queue.items.slice(queue.head);16 queue.head = 0;17 }18 return value;19}20
21const size = () => queue.items.length - queue.head;22
23for (const value of [3, 7, 5]) {24 enqueue(value);25 console.log(`enqueue ${value} -> size ${size()}`);26}27
28// Dequeue from the front: first in, first out, amortized O(1)29while (size() > 0) {30 console.log(`dequeue ${dequeue()} -> size ${size()}`);31}32
33console.log('empty:', size() === 0);كود Queue بلغة Java
1import java.util.ArrayDeque;2import java.util.Queue;3
4public class Main {5 public static void main(String[] args) {6 Queue<Integer> queue = new ArrayDeque<>();7
8 // Enqueue three values at the rear9 for (int value : new int[] {3, 7, 5}) {10 queue.add(value);11 System.out.println("enqueue " + value + " -> " + queue);12 }13
14 // Dequeue them from the front: first in, first out15 while (!queue.isEmpty()) {16 int value = queue.remove();17 System.out.println("dequeue " + value + " -> " + queue);18 }19
20 System.out.println("empty: " + queue.isEmpty());21 }22}كود Queue بلغة C++
1#include <iostream>2#include <queue>3
4int main() {5 std::queue<int> queue;6
7 // Enqueue three values at the rear8 for (int value : {3, 7, 5}) {9 queue.push(value);10 std::cout << "enqueue " << value << " -> size " << queue.size() << "\n";11 }12
13 // Dequeue them from the front: first in, first out14 while (!queue.empty()) {15 int value = queue.front();16 queue.pop();17 std::cout << "dequeue " << value << " -> size " << queue.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << queue.empty() << "\n";21 return 0;22}كود Queue بلغة C
1#include <stdio.h>2
3#define CAP 164
5int queue[CAP];6int front = 0;7int rear = 0; /* index of the next free slot */8
9int main(void) {10 int values[3] = {3, 7, 5};11
12 /* Enqueue three values at the rear */13 for (int i = 0; i < 3; i++) {14 queue[rear++] = values[i];15 printf("enqueue %d -> size %d\n", values[i], rear - front);16 }17
18 /* Dequeue them from the front: first in, first out */19 while (front < rear) {20 int value = queue[front++];21 printf("dequeue %d -> size %d\n", value, rear - front);22 }23
24 printf("empty: %d\n", front == rear);25 return 0;26}الأسئلة الشائعة حول الطابور
ماذا تعني FIFO؟
ما الفرق بين الطابور والمكدس؟
O(1)، لكن الطابور يزيل من المقدمة (FIFO) بينما يزيل المكدس من الطرف نفسه الذي أضاف إليه (LIFO). وجدولا التعقيد لديهما متطابقان فيما عدا ذلك.ما هي عمليات الطابور الأساسية؟
enqueue قيمة عند المؤخرة، وتزيل dequeue قيمة المقدمة وتعيدها، ويقرأ peek (أو front) المقدمة دون إزالتها، ويخبرك is_empty بما إذا كان هناك شيء ينتظر. العمليات الأربع كلها O(1).لماذا تكون الإزالة بطيئة إن استخدمت مصفوفة عادية؟
O(n). تتجنب التنفيذات الحقيقية ذلك بمخزن حلقي يقدّم فهرس المقدمة، أو بقائمة مترابطة لها مؤشر رأس. ويفعل هذا نيابةً عنك collections.deque في بايثون وArrayDeque في جافا، بينما لا يفعله list.pop(0).ما هو الطابور الدائري؟
n يعمل إلى ما لا نهاية بدل أن يخرج عن نهاية المصفوفة.