
شرح هيكل بيانات الطابور
الطابور (Queue) هو هيكل بيانات خطي يتبع مبدأ "الوارد أولاً، صادر أولاً" (First In, First Out)، والمعروف اختصاراً بـ FIFO. هذا يعني أن العنصر الأول الذي تمت إضافته إلى الطابور هو أول عنصر تتم إزالته منه.
تعتبر الطوابير مهمة جداً في علوم الحاسوب وهندسة البرمجيات. تُستخدم في جدولة المهام، طوابير الطباعة، طوابير الرسائل، معالجة الطلبات، البحث بالعرض (Breadth-First Search)، المهام الخلفية، أنظمة التشغيل، والعديد من أنظمة البرمجيات الحقيقية.
مقدمة
تساعد هياكل البيانات المطورين على تنظيم البيانات وفقاً لكيفية الوصول إلى تلك البيانات ومعالجتها. تدعم المصفوفات (Arrays)، القوائم المرتبطة (Linked Lists)، المكدسات (Stacks)، الطوابير (Queues)، الأشجار (Trees)، الرسوم البيانية (Graphs)، وجداول التجزئة (Hash Tables) أنماط وصول مختلفة.
الطابور (Queue) هو أحد هياكل البيانات العملية جداً لأنه يمثل خطوط الانتظار. يجب معالجة العنصر الأول الذي يدخل الطابور أولاً. هذا السلوك شائع في الحياة الواقعية وأنظمة البرمجيات.
على سبيل المثال، يتم عادةً خدمة العملاء الذين ينتظرون في مكتب خدمة بالترتيب الذي وصلوا به. تتعامل الطابعة مع المستندات بالترتيب الذي تم إرسالها به. قد يعالج الخادم الطلبات بالترتيب الذي تصل به. هذه كلها سلوكيات تشبه الطوابير.
ما هو الطابور؟
الطابور هو مجموعة من العناصر حيث تتم الإضافة في أحد الطرفين وتتم الإزالة من الطرف الآخر.
الطرف الذي تتم فيه إضافة العناصر يسمى عادةً "الخلف" (rear) أو "نهاية" الطابور. الطرف الذي تتم فيه إزالة العناصر يسمى "الأمام" (front) للطابور.
يتبع الطابور قاعدة FIFO:
First In, First Outهذا يعني أن العنصر الذي انتظر أطول فترة تتم معالجته أولاً.
الفكرة الرئيسية بسيطة:
أضف عناصر جديدة إلى الخلف.
قم بإزالة العناصر من الأمام.
اقرأ العنصر الأمامي دون إزالته عند الحاجة.
تحقق مما إذا كان الطابور فارغاً قبل إزالة عنصر.
تم تصميم الطابور للمعالجة بالترتيب، وليس للوصول العشوائي.
تشبيه الطابور في الحياة الواقعية
مثال شائع للطابور في الحياة الواقعية هو صف من الأشخاص ينتظرون عند أمين الصندوق.
Front → Person A → Person B → Person C ← Rearوصل الشخص أ أولاً، لذا يتم خدمة الشخص أ أولاً. وصل الشخص ج أخيراً، لذا ينتظر الشخص ج خلف الآخرين.
إذا وصل شخص جديد، ينضم إلى الخلف:
Front → Person A → Person B → Person C → Person D ← Rearعندما يخدم أمين الصندوق شخصاً ما، يغادر الشخص أ أولاً:
Front → Person B → Person C → Person D ← Rearهذا هو بالضبط كيف يعمل الطابور في البرمجة.
مبدأ FIFO
FIFO تعني "الوارد أولاً، صادر أولاً" (First In, First Out). هذا يعني أن العنصر الأول الذي يتم إدخاله في الطابور هو أول عنصر يتم إخراجه من الطابور.
على سبيل المثال، إذا قمنا بإضافة (enqueue) قيم بهذا الترتيب:
10, 20, 30يبدو الطابور كالتالي:
Front → 10 → 20 → 30 ← Rearإذا قمنا بإزالة (dequeue) عنصراً، تتم إزالة القيمة 10 أولاً لأنها دخلت الطابور أولاً.
يختلف هذا السلوك عن المكدس (Stack)، الذي يتبع مبدأ "الوارد أخيراً، صادر أولاً" (LIFO).
عمليات الطابور الأساسية
يدعم الطابور عادةً مجموعة صغيرة من العمليات المهمة.
enqueue: يضيف عنصراً إلى خلف الطابور.
dequeue: يزيل ويعيد العنصر الأمامي.
front: يعيد العنصر الأمامي دون إزالته.
rear: يعيد العنصر الخلفي دون إزالته.
isEmpty: يتحقق مما إذا كان الطابور لا يحتوي على عناصر.
size: يعيد عدد العناصر في الطابور.
تجعل هذه العمليات الطوابير مفيدة كلما كان يجب معالجة العناصر بترتيب وصولها.
عملية الإضافة (Enqueue)
عملية الإضافة (enqueue) تضيف عنصراً جديداً إلى خلف الطابور.
إذا كان الطابور فارغاً وأضفنا 10، يصبح الطابور:
Front → 10 ← Rearإذا أضفنا 20، يصبح الطابور:
Front → 10 → 20 ← Rearإذا أضفنا 30، يصبح الطابور:
Front → 10 → 20 → 30 ← Rearيتم إضافة كل عنصر جديد بعد العنصر الخلفي الحالي.
عملية الإزالة (Dequeue)
عملية الإزالة (dequeue) تزيل العنصر الأمامي من الطابور وتعيده.
لنفترض هذا الطابور:
Front → 10 → 20 → 30 ← Rearإذا قمنا باستدعاء dequeue، تتم إزالة القيمة 10.
Removed: 10يصبح الطابور:
Front → 20 → 30 ← Rearإذا قمنا باستدعاء dequeue مرة أخرى، تتم إزالة القيمة 20.
Removed: 20يصبح الطابور:
Front → 30 ← Rearيجب استخدام عملية الإزالة (dequeue) بحذر عندما يكون الطابور فارغاً. تسمى الإزالة من طابور فارغ عادةً "فيضان الطابور" (queue underflow).
عملية الأمام (Front)
عملية الأمام (front) تعيد العنصر الأول في الطابور دون إزالته.
لنفترض هذا الطابور:
Front → 10 → 20 → 30 ← Rearإذا قمنا باستدعاء front، تكون النتيجة:
10يبقى الطابور دون تغيير:
Front → 10 → 20 → 30 ← Rearهذه العملية مفيدة عندما نحتاج إلى فحص العنصر التالي الذي سيتم معالجته.
عملية الخلف (Rear)
عملية الخلف (rear) تعيد العنصر الأخير في الطابور دون إزالته.
لنفترض هذا الطابور:
Front → 10 → 20 → 30 ← Rearإذا قمنا باستدعاء rear، تكون النتيجة:
30يبقى الطابور دون تغيير.
عملية الخلف (rear) مفيدة عندما نحتاج إلى فحص أحدث عنصر تمت إضافته.
عملية التحقق من الفراغ (isEmpty)
عملية التحقق من الفراغ (isEmpty) تتحقق مما إذا كان الطابور يحتوي على أي عناصر.
إذا كان الطابور لا يحتوي على عناصر:
[]فإن isEmpty تعيد true.
إذا كان الطابور يحتوي على عناصر:
[10, 20, 30]فإن isEmpty تعيد false.
هذه العملية مهمة قبل استدعاء dequeue أو front أو rear.
مثال تشغيل يدوي
دعونا نجري تشغيلاً يدوياً لسلسلة من عمليات الطابور.
enqueue(10)
enqueue(20)
enqueue(30)
front()
dequeue()
enqueue(40)
dequeue()
rear()نبدأ بطابور فارغ:
Queue: []الخطوة 1: enqueue(10)
نضيف 10 إلى خلف الطابور.
Front → 10 ← Rearالخطوة 2: enqueue(20)
نضيف 20 بعد 10.
Front → 10 → 20 ← Rearالخطوة 3: enqueue(30)
نضيف 30 بعد 20.
Front → 10 → 20 → 30 ← Rearالخطوة 4: front()
العنصر الأمامي هو 10.
front() returns 10لا يتغير الطابور:
Front → 10 → 20 → 30 ← Rearالخطوة 5: dequeue()
يتم إزالة العنصر الأمامي 10.
dequeue() returns 10يصبح الطابور:
Front → 20 → 30 ← Rearالخطوة 6: enqueue(40)
نضيف 40 إلى الخلف.
Front → 20 → 30 → 40 ← Rearالخطوة 7: dequeue()
يتم إزالة العنصر الأمامي 20.
dequeue() returns 20يصبح الطابور:
Front → 30 → 40 ← Rearالخطوة 8: rear()
العنصر الخلفي هو 40.
rear() returns 40يبقى الطابور دون تغيير:
Front → 30 → 40 ← Rearيوضح هذا التشغيل اليدوي سلوك FIFO بوضوح. القيمة الأولى المضافة هي أول قيمة تتم إزالتها.
تنفيذ الطابور باستخدام مصفوفة
يمكن تنفيذ الطابور باستخدام مصفوفة. أبسط طريقة هي إضافة العناصر في نهاية المصفوفة وإزالة العناصر من البداية.
على سبيل المثال:
[10, 20, 30]القيمة 10 هي الأمام، والقيمة 30 هي الخلف.
ومع ذلك، يمكن أن تكون الإزالة من بداية المصفوفة غير فعالة في بعض اللغات لأن العناصر المتبقية قد تحتاج إلى الإزاحة. تنفيذ أفضل يستخدم مؤشراً للأمام (front index) لتجنب الإزاحة عند كل عملية إزالة.
شبه كود الطابور
class Queue:
items = empty array
frontIndex = 0
enqueue(value):
add value to end of items
dequeue():
if queue is empty:
return error
value = items[frontIndex]
frontIndex = frontIndex + 1
return value
front():
if queue is empty:
return error
return items[frontIndex]
isEmpty():
return frontIndex >= length of items
size():
return length of items - frontIndexيتجنب هذا الإصدار إزالة العناصر مباشرة من بداية المصفوفة.
مثال طابور بلغة PHP
إليك تطبيق PHP نظيف لطابور باستخدام مصفوفة ومؤشر أمامي.
<?php
class Queue
{
private array $items = [];
private int $frontIndex = 0;
public function enqueue(mixed $value): void
{
$this->items[] = $value;
}
public function dequeue(): mixed
{
if ($this->isEmpty()) {
throw new RuntimeException('Cannot dequeue from an empty queue.');
}
$value = $this->items[$this->frontIndex];
$this->frontIndex++;
return $value;
}
public function front(): mixed
{
if ($this->isEmpty()) {
throw new RuntimeException('Cannot read front from an empty queue.');
}
return $this->items[$this->frontIndex];
}
public function rear(): mixed
{
if ($this->isEmpty()) {
throw new RuntimeException('Cannot read rear from an empty queue.');
}
return $this->items[count($this->items) - 1];
}
public function isEmpty(): bool
{
return $this->frontIndex >= count($this->items);
}
public function size(): int
{
return count($this->items) - $this->frontIndex;
}
}
$queue = new Queue();
$queue->enqueue(10);
$queue->enqueue(20);
$queue->enqueue(30);
echo $queue->front(); // 10
echo PHP_EOL;
echo $queue->dequeue(); // 10
echo PHP_EOL;
$queue->enqueue(40);
echo $queue->rear(); // 40يتجنب هذا التنفيذ استخدام array_shift، والتي يمكن أن تكون غير فعالة للمصفوفات الكبيرة لأنها قد تزحزح العناصر داخلياً.
مثال طابور بلغة JavaScript
إليك تطبيق JavaScript يستخدم مصفوفة ومؤشر أمامي.
class Queue {
constructor() {
this.items = [];
this.frontIndex = 0;
}
enqueue(value) {
this.items.push(value);
}
dequeue() {
if (this.isEmpty()) {
throw new Error('Cannot dequeue from an empty queue.');
}
const value = this.items[this.frontIndex];
this.frontIndex++;
return value;
}
front() {
if (this.isEmpty()) {
throw new Error('Cannot read front from an empty queue.');
}
return this.items[this.frontIndex];
}
rear() {
if (this.isEmpty()) {
throw new Error('Cannot read rear from an empty queue.');
}
return this.items[this.items.length - 1];
}
isEmpty() {
return this.frontIndex >= this.items.length;
}
size() {
return this.items.length - this.frontIndex;
}
}
const queue = new Queue();
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
console.log(queue.front()); // 10
console.log(queue.dequeue()); // 10
queue.enqueue(40);
console.log(queue.rear()); // 40يتجنب إصدار JavaScript هذا استخدام shift() لكل عملية إزالة. يمكن أن يكون استخدام shift() أقل كفاءة لأنه قد يحرك جميع العناصر المتبقية.
تنفيذ الطابور باستخدام قائمة مرتبطة
يمكن أيضاً تنفيذ الطابور باستخدام قائمة مرتبطة (Linked List). في هذا التنفيذ، يحتفظ الطابور بمرجع لكل من الأمام والخلف.
يشير الأمام إلى العقدة التي ستتم إزالتها تالياً. يشير الخلف إلى العقدة التي تتم فيها إضافة العناصر الجديدة.
Front → [10] → [20] → [30] ← Rearمع وجود مراجع لكل من الأمام والخلف، يمكن أن تكون كل من عمليتي الإضافة (enqueue) والإزالة (dequeue) O(1).
مثال طابور القائمة المرتبطة بلغة JavaScript
class QueueNode {
constructor(data) {
this.data = data;
this.next = null;
}
}
class LinkedListQueue {
constructor() {
this.frontNode = null;
this.rearNode = null;
this.length = 0;
}
enqueue(value) {
const newNode = new QueueNode(value);
if (this.rearNode === null) {
this.frontNode = newNode;
this.rearNode = newNode;
} else {
this.rearNode.next = newNode;
this.rearNode = newNode;
}
this.length++;
}
dequeue() {
if (this.isEmpty()) {
throw new Error('Cannot dequeue from an empty queue.');
}
const removedValue = this.frontNode.data;
this.frontNode = this.frontNode.next;
if (this.frontNode === null) {
this.rearNode = null;
}
this.length--;
return removedValue;
}
front() {
if (this.isEmpty()) {
throw new Error('Cannot read front from an empty queue.');
}
return this.frontNode.data;
}
rear() {
if (this.isEmpty()) {
throw new Error('Cannot read rear from an empty queue.');
}
return this.rearNode.data;
}
isEmpty() {
return this.frontNode === null;
}
size() {
return this.length;
}
}هذا التنفيذ القائم على القائمة المرتبطة فعال لأنه لا يحتاج إلى إزاحة أو تحريك مؤشرات.
طابور المصفوفة مقابل طابور القائمة المرتبطة
يمكن استخدام كل من المصفوفات والقوائم المرتبطة لتنفيذ الطابور.
طابور المصفوفة بسيط ومريح، خاصة في اللغات عالية المستوى. طابور القائمة المرتبطة مفيد عندما نريد إدخال وإزالة ديناميكية فعالة دون القلق بشأن إزاحة العناصر.
الاختلافات الرئيسية تشمل:
طابور المصفوفة سهل التنفيذ.
طابور القائمة المرتبطة يستخدم العقد والمراجع.
يجب أن يتجنب طابور المصفوفة عمليات الإزالة من الأمام المكلفة.
يمكن لطابور القائمة المرتبطة دعم الإضافة والإزالة O(1) مع مراجع الأمام والخلف.
قد يستخدم طابور المصفوفة مساحة غير مستخدمة إذا استمر مؤشر الأمام في الزيادة.
يستخدم طابور القائمة المرتبطة ذاكرة إضافية لمراجع العقد.
لغرض التعلم، كلا التنفيذين مفيدان. للإنتاج، قد تكون هياكل الطوابير المدمجة أو المكتبات المحسّنة أفضل.
أنواع الطوابير
هناك عدة أنواع من الطوابير. كل نوع يحل نوعاً مختلفاً من المشكلات.
الأنواع الرئيسية هي:
الطابور البسيط.
الطابور الدائري (Circular Queue).
طابور الأولوية (Priority Queue).
الطابور المزدوج (Deque).
يساعد فهم هذه الأنواع المطورين على اختيار هيكل الطابور الصحيح لمواقف مختلفة.
الطابور البسيط
يتبع الطابور البسيط قاعدة FIFO العادية. يتم إدخال العناصر في الخلف وإزالتها من الأمام.
Front → 10 → 20 → 30 ← Rearهذا هو الطابور القياسي المستخدم في معظم الأمثلة للمبتدئين.
الطوابير البسيطة مفيدة لـ:
معالجة المهام الأساسية.
معالجة الطلبات.
المعالجة بناءً على الترتيب.
تعليم سلوك FIFO.
الطابور الدائري
يربط الطابور الدائري نهاية الطابور ببدايته منطقياً. غالباً ما يتم تنفيذه باستخدام مصفوفة ذات حجم ثابت.
الفكرة الرئيسية هي أنه عندما يصل الخلف إلى نهاية المصفوفة، يمكن أن يلتف للعودة إلى البداية إذا كانت هناك مساحة خالية.
Array size: 5
[_, 20, 30, 40, _]
front rearإذا كانت هناك مساحة في البداية، يمكن للخلف الانتقال إلى هناك:
[50, 20, 30, 40, _]
rear frontالطوابير الدائرية مفيدة عند العمل مع مخازن مؤقتة ذات حجم ثابت، وبيانات التدفق، وجدولة نظام التشغيل.
طابور الأولوية
يزيل طابور الأولوية العناصر بناءً على الأولوية بدلاً من ترتيب الوصول.
على سبيل المثال، في نظام طوارئ بالمستشفى، قد يتم علاج مريض ذي حالة حرجة قبل شخص وصل مبكراً ولكن لديه حالة أقل إلحاحاً.
Task A: priority 2
Task B: priority 5
Task C: priority 1إذا تمت معالجة قيم الأولوية الأعلى أولاً، تتم إزالة المهمة B قبل المهمة A والمهمة C.
غالباً ما يتم تنفيذ طوابير الأولوية باستخدام الأكوام (heaps) لأن الأكوام توفر إدخالاً وإزالة فعالين لأعلى أو أدنى عنصر أولوية.
الطابور المزدوج (Deque)
الطابور المزدوج (Deque)، ينطق "دي-كي"، يسمح بالإدخال والإزالة من كلا الطرفين.
Front ⇄ [10] ⇄ [20] ⇄ [30] ⇄ Rearيمكن للطابور المزدوج أن يتصرف كطابور عادي أو كمكدس اعتماداً على كيفية استخدامه.
تشمل عمليات الطابور المزدوج:
الإضافة في الأمام.
الإضافة في الخلف.
الإزالة من الأمام.
الإزالة من الخلف.
الطوابير المزدوجة مفيدة في خوارزميات النافذة المنزلقة (sliding window)، وأنظمة التراجع/الإعادة (undo-redo)، ومشاكل الجدولة المتقدمة.
التعقيد الزمني لعمليات الطابور
يصف التعقيد الزمني كيف ينمو وقت تشغيل العملية مع زيادة عدد العناصر.
بالنسبة للطابور القياسي ذي التنفيذ الجيد، فإن التعقيدات الشائعة هي:
enqueue: O(1)
dequeue: O(1)
front: O(1)
rear: O(1)
isEmpty: O(1)
size: O(1)
search: O(n)
تعتبر عمليتا الإضافة (enqueue) والإزالة (dequeue) عمليات ذات وقت ثابت عند تنفيذ الطابور بشكل صحيح.
لماذا الإضافة (Enqueue) هي O(1)
تقوم عملية الإضافة (enqueue) بإضافة عنصر جديد إلى خلف الطابور.
Before:
Front → 10 → 20 ← Rear
enqueue(30)
After:
Front → 10 → 20 → 30 ← Rearإذا كان موقع الخلف معروفا، فإن إضافة العنصر الجديد لا تتطلب مسح الطابور بأكمله.
لذلك، فإن الإضافة هي:
O(1)لماذا الإزالة (Dequeue) هي O(1)
تقوم عملية الإزالة (dequeue) بإزالة العنصر الأمامي.
Before:
Front → 10 → 20 → 30 ← Rear
dequeue()
After:
Front → 20 → 30 ← Rearإذا كان موقع الأمام معروفا، يمكن إجراء إزالة العنصر الأمامي مباشرة.
لذلك، فإن الإزالة هي:
O(1)يفترض هذا تنفيذاً جيداً. إذا استخدم تنفيذ المصفوفة عملية إزاحة مكلفة في كل مرة، فقد تصبح الإزالة O(n).
لماذا البحث هو O(n)
البحث داخل الطابور ليس الغرض الرئيسي للهيكل. إذا احتجنا إلى العثور على قيمة معينة، فقد نحتاج إلى فحص العناصر واحداً تلو الآخر.
Front → 10 → 20 → 30 ← Rearللبحث عن 30، قد نحتاج إلى التحقق من 10، ثم 20، ثم 30.
في أسوأ الحالات، يتم فحص جميع العناصر، لذا فإن البحث هو:
O(n)التعقيد المكاني للطابور
التعقيد المكاني للطابور هو O(n)، حيث n هو عدد العناصر المخزنة في الطابور.
إذا كان الطابور يخزن 100 عنصر، فإنه يحتاج إلى ذاكرة لـ 100 عنصر. إذا كان يخزن 1000 عنصر، فإنه يحتاج إلى ذاكرة لـ 1000 عنصر.
Space complexity = O(n)إذا تم تنفيذ الطابور باستخدام عقد مرتبطة، فإن كل عقدة تخزن أيضاً مرجعاً للعقدة التالية. إذا تم تنفيذه باستخدام مصفوفة، فإن الذاكرة تعتمد على سعة المصفوفة واستراتيجية التنفيذ.
فيضان الطابور (Queue Overflow) وفيضان الطابور السفلي (Queue Underflow)
يحدث فيضان الطابور (Queue overflow) عندما يكون للطابور سعة محدودة ويحاول برنامج ما إدخال عناصر أكثر مما يمكن للطابور استيعابه.
Queue capacity: 3
Queue: [10, 20, 30]
enqueue(40) → overflowيحدث فيضان الطابور السفلي (Queue underflow) عندما يحاول برنامج ما إزالة أو قراءة عنصر من طابور فارغ.
Queue: []
dequeue() → underflowيجب أن تتعامل تطبيقات الطوابير الجيدة مع هذه الحالات بأمان عن طريق طرح استثناء (exception)، أو إعادة قيمة فارغة (null)، أو استخدام استراتيجية خطأ واضحة.
الطوابير والبحث بالعرض (Breadth-First Search)
أحد أهم الاستخدامات الخوارزمية للطابور هو البحث بالعرض (Breadth-First Search)، والمعروف أيضاً باسم BFS.
يستكشف BFS العقد مستوى بمستوى. يُستخدم الطابور لتذكر أي عقدة يجب معالجتها تالياً.
على سبيل المثال، في رسم بياني أو شجرة:
Start with node A
enqueue(A)
While queue is not empty:
dequeue a node
visit it
enqueue its unvisited neighborsيضمن سلوك FIFO معالجة العقد التي تم اكتشافها مبكراً بشكل أسرع، مما يؤدي إلى اجتياز مستوى بمستوى.
الطوابير وجدولة المهام
تُستخدم الطوابير في أنظمة جدولة المهام حيث يجب معالجة المهام بالترتيب الذي تصل به.
على سبيل المثال:
Task 1: Send email
Task 2: Generate report
Task 3: Resize imageيمكن للعامل معالجة المهام من أمام الطابور:
dequeue() → Send email
dequeue() → Generate report
dequeue() → Resize imageهذا النموذج شائع في أنظمة المهام الخلفية ووسطاء الرسائل (message brokers).
الطوابير ووظائف الطباعة
طابور الطباعة هو مثال كلاسيكي للطابور. إذا تم إرسال ثلاث مستندات إلى طابعة، فإنها عادةً ما تتم طباعتها بالترتيب الذي وصلت به.
Front → Document A → Document B → Document C ← Rearتتم طباعة المستند A أولاً، ثم المستند B، ثم المستند C.
هذا هو سلوك FIFO في نظام حقيقي.
الطوابير وطلبات الويب
تُستخدم الطوابير أيضاً في أنظمة الويب. عندما تصل العديد من الطلبات أو المهام الخلفية، يمكن للطابور الاحتفاظ بها حتى تصبح العوامل (workers) جاهزة لمعالجتها.
على سبيل المثال، قد يستخدم تطبيق Laravel الطوابير لـ:
إرسال رسائل البريد الإلكتروني.
معالجة الملفات التي تم تحميلها.
إنشاء التقارير.
إرسال الإشعارات.
تشغيل المهام البطيئة في الخلفية.
هذا يمنع العمليات البطيئة من حظر طلب المستخدم الرئيسي.
مثال عملي: طابور مهام بسيط بلغة JavaScript
إليك مثال بسيط لطابور مهام:
class TaskQueue {
constructor() {
this.queue = [];
this.frontIndex = 0;
}
addTask(task) {
this.queue.push(task);
}
processTask() {
if (this.frontIndex >= this.queue.length) {
return null;
}
const task = this.queue[this.frontIndex];
this.frontIndex++;
return task;
}
}
const tasks = new TaskQueue();
tasks.addTask('Send email');
tasks.addTask('Generate report');
tasks.addTask('Resize image');
console.log(tasks.processTask()); // Send email
console.log(tasks.processTask()); // Generate reportأول مهمة تمت إضافتها هي أول مهمة تتم معالجتها.
الطابور مقابل المكدس
الطوابير والمكدسات كلاهما هياكل بيانات خطية، لكنهما يتبعان قواعد وصول مختلفة.
الطابور يتبع FIFO:
First In, First Outالمكدس يتبع LIFO:
Last In, First Outعلى سبيل المثال، الطابور يشبه صفاً من الناس ينتظرون. أول شخص يصل يتم خدمته أولاً.
المكدس يشبه كومة من الأطباق. آخر طبق تم وضعه في الأعلى هو أول طبق يتم إزالته.
الاختلافات الرئيسية تشمل:
الطابور يزيل أقدم عنصر أولاً.
المكدس يزيل أحدث عنصر أولاً.
يستخدم الطابور enqueue و dequeue.
يستخدم المكدس push و pop.
الطابور مفيد للجدولة وخطوط الانتظار.
المكدس مفيد للتراجع، العودية (recursion)، والتراجع (backtracking).
متى يجب على المطورين استخدام الطابور؟
يجب على المطورين استخدام الطابور عندما تحتاج العناصر إلى المعالجة بنفس الترتيب الذي تصل به.
الطوابير مفيدة عندما:
يلزم سلوك "الوارد أولاً، صادر أولاً".
يجب معالجة المهام بترتيب وصولها.
تحتاج الطلبات إلى الانتظار قبل المعالجة.
هناك حاجة إلى البحث بالعرض (BFS).
يتم استخدام المهام الخلفية.
هناك حاجة لمعالجة الرسائل.
هناك حاجة للجدولة أو التخزين المؤقت.
الإشارة الرئيسية هي سلوك FIFO. إذا كان يجب معالجة أقدم عنصر أولاً، فقد يكون الطابور هو الهيكل الصحيح.
متى لا تستخدم الطابور
الطابور ليس الهيكل المناسب عندما يجب معالجة أحدث عنصر أولاً، أو عندما يكون الوصول العشوائي مطلوباً.
يجب على المطورين تجنب الطابور عندما:
يلزم سلوك "الوارد أخيراً، صادر أولاً".
هناك حاجة للوصول العشوائي السريع حسب المؤشر.
تتطلب المعالجة بناءً على الأولوية.
البحث المتكرر هو العملية الرئيسية.
ترتيب الوصول لا يهم.
في تلك الحالات، قد يكون المكدس، أو المصفوفة، أو جدول التجزئة، أو طابور الأولوية، أو هيكل بيانات آخر أكثر ملاءمة.
أخطاء شائعة عند تعلم الطوابير
غالباً ما يفهم المبتدئون فكرة FIFO ولكنهم يرتكبون أخطاء في التنفيذ أو تحليل التعقيد.
الأخطاء الشائعة تشمل:
الخلط بين الطابور والمكدس.
الإزالة من النهاية الخاطئة.
استخدام إزاحة المصفوفة المكلفة دون فهم الأداء.
نسيان معالجة حالات الطابور الفارغ.
الافتراض بأن البحث هو O(1).
عدم تحديث الأمام والخلف بشكل صحيح في طوابير القائمة المرتبطة.
إنشاء منطق التفاف خاطئ في الطوابير الدائرية.
القاعدة الأكثر أماناً بسيطة: الإضافة في الخلف والإزالة من الأمام.
قائمة تحقق عملية لفهم الطوابير
قبل الانتقال إلى هياكل البيانات المتقدمة وخوارزميات الرسوم البيانية، يجب أن يكون المطورون قادرين على الإجابة على هذه الأسئلة:
ماذا يعني FIFO؟
ما هو أمام الطابور؟
ما هو خلف الطابور؟
ماذا تفعل عملية enqueue؟
ماذا تفعل عملية dequeue؟
لماذا تعتبر enqueue و dequeue O(1) في تنفيذ جيد؟
ما هو فيضان الطابور السفلي (queue underflow)؟
ما هو الفرق بين الطابور والمكدس؟
ما هو الطابور الدائري؟
لماذا يستخدم BFS الطابور؟
إذا كانت هذه الأسئلة واضحة، فإن المطور يفهم المنطق الحقيقي وراء الطوابير.
مزايا الطوابير
للطوابير العديد من المزايا المهمة.
إنها بسيطة الفهم.
تدعم بشكل طبيعي سلوك FIFO.
إنها مفيدة للجدولة وخطوط الانتظار.
يمكن أن تكون enqueue و dequeue O(1).
إنها ضرورية لـ BFS.
إنها مفيدة في أنظمة المهام الخلفية ومعالجة الرسائل.
هذه المزايا تجعل الطوابير واحدة من أهم هياكل البيانات في علوم الحاسوب.
عيوب الطوابير
للطوابير أيضاً قيود.
لا توفر وصولاً عشوائياً سريعاً.
البحث داخل الطابور هو O(n).
إنها غير مناسبة لسلوك LIFO.
المعالجة بناءً على الأولوية تتطلب طابور أولوية بدلاً من ذلك.
يجب تصميم تطبيقات المصفوفات بعناية لتجنب الإزاحة غير الفعالة.
هذه القيود طبيعية. إنها تظهر ببساطة أن الطوابير مصممة للمعالجة بالترتيب، وليس لكل أنماط الوصول الممكنة.
الطوابير في مشاريع البرمجيات الحقيقية
تظهر الطوابير في مشاريع البرمجيات الحقيقية بأشكال عديدة. قد يستخدم المطورون الطوابير المدمجة، أو وسطاء الرسائل، أو أنظمة المهام، أو أدوات الطوابير في الأطر (frameworks) بدلاً من تنفيذ فئات الطوابير يدوياً.
الاستخدامات الشائعة في العالم الحقيقي تشمل:
معالجة المهام الخلفية.
إرسال البريد الإلكتروني.
أنظمة الإشعارات.
معالجة وظائف الطباعة.
تخزين الطلبات مؤقتاً (Request buffering).
جدولة نظام التشغيل.
طوابير الرسائل.
البحث بالعرض في الأشجار والرسوم البيانية.
يساعد فهم الطوابير المطورين على فهم كيفية عمل أنظمة المعالجة غير المتزامنة والمنظمة داخلياً.
خاتمة
الطابور هو هيكل بيانات خطي يتبع مبدأ "الوارد أولاً، صادر أولاً". أول عنصر تتم إضافته إلى الطابور هو أول عنصر تتم إزالته.
عمليات الطابور الرئيسية هي enqueue، dequeue، front، rear، isEmpty، و size. تقوم enqueue بإضافة عنصر إلى الخلف، بينما تقوم dequeue بإزالة العنصر الأمامي.
مع التنفيذ الجيد، تعتبر enqueue، dequeue، front، rear، isEmpty، و size هي O(1). البحث داخل الطابور هو O(n) لأن الهيكل غير مصمم للوصول العشوائي.
يمكن تنفيذ الطوابير باستخدام المصفوفات أو القوائم المرتبطة. كما أن لديها اختلافات مهمة مثل الطابور الدائري، طابور الأولوية، والطابور المزدوج (Deque).
بالنسبة للمطورين الذين يتعلمون هياكل البيانات والخوارزميات، فإن الطوابير أساسية. إنها تعلم سلوك FIFO، المعالجة بالترتيب، منطق الجدولة، اجتياز BFS، وأساس العديد من الأنظمة الواقعية مثل المهام الخلفية، وطوابير الرسائل، ومعالجة الطلبات.

