تنفيذ الطابور وDeque
أنشئ طابورًا باستخدام deque في Python، ونفّذ طابورًا دائريًا، وحل مسألة أقصى قيمة في نافذة منزلقة باستخدام deque رتيب
تنفيذ الطابور وDeque درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
بنية بيانات الطابور
الطابور queue هو بنية بيانات تعمل وفق مبدأ الوارد أولًا، الصادر أولًا (FIFO). فأول عنصر يُدرج في الطابور هو أول عنصر يُزال منه، مثل صف الانتظار عند صندوق الدفع في متجر. العمليتان الأساسيتان هما enqueue (الإضافة إلى النهاية) وdequeue (الإزالة من البداية). ويجب أن تستغرق كلتاهما O(1) ليكون الطابور فعالًا.
قد يبدو استخدام قائمة Python كطابور خيارًا مناسبًا، لكنه خاطئ: إذ تستغرق list.pop(0) زمن O(n) لأنها تحرك جميع العناصر. الأداة الصحيحة هي collections.deque، التي توفر العمليات appendleft وappend وpopleft وpop في O(1).
from collections import deque
queue = deque()
# Enqueue (add to rear)
queue.append(10)
queue.append(20)
queue.append(30)
print('Queue:', queue) # deque([10, 20, 30])
# Peek front
print('Front:', queue[0]) # 10
# Dequeue (remove from front)
print('Dequeued:', queue.popleft()) # 10
print('Queue after:', queue) # deque([20, 30])فئة Queue باستخدام deque
غلّف deque داخل فئة Queue مع عمليات ذات أسماء واضحة لتتوافق مع ما يتوقعه المحاورون. تستدعي enqueue داخليًا append، بينما تستدعي dequeue داخليًا popleft. وتقرأ عملية peek القيمة queue[0] دون إزالتها.
from collections import deque
class Queue:
def __init__(self):
self._data = deque()
def enqueue(self, val):
self._data.append(val)
def dequeue(self):
if self.is_empty():
raise IndexError('dequeue from empty queue')
return self._data.popleft()
def peek(self):
if self.is_empty():
raise IndexError('peek at empty queue')
return self._data[0]
def is_empty(self):
return len(self._data) == 0
def __len__(self):
return len(self._data)
q = Queue()
q.enqueue(1); q.enqueue(2); q.enqueue(3)
print(q.peek()) # 1
print(q.dequeue()) # 1
print(len(q)) # 2BFS باستخدام طابور
التطبيق الكلاسيكي للطابور هو البحث بعرض الرسم (BFS). أدرج الجذر في الطابور؛ وما دام الطابور غير فارغ، أزل عقدة، وعالجها، ثم أدرج جيرانها الذين لم تُزَر بعد. وبما أننا نعالج العقد مستوىً تلو الآخر، يعثر BFS طبيعيًا على أقصر مسار في رسم بياني غير موزون. ويحتوي الطابور دائمًا على عقد من مستويين متجاورين كحد أقصى.
from collections import deque
def bfs(graph, start):
visited = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
visited.add(neighbour)
queue.append(neighbour)
return order
graph = {0:[1,2], 1:[0,3,4], 2:[0,5], 3:[1], 4:[1], 5:[2]}
print(bfs(graph, 0)) # [0, 1, 2, 3, 4, 5]الطابور الدائري (LeetCode 622)
LeetCode 622 «تصميم طابور دائري»: تنفيذ طابور ذي سعة ثابتة يلتف حول نفسه. استخدم مصفوفة حجمها k ومؤشرين: head وtail. أدرج العناصر عند tail، وأزلها عند head، واحسب المواضع باستخدام باقي القسمة على k. يميز متغير count بين حالتي الامتلاء والفراغ (ففي كلتا الحالتين يكون head == tail وفق باقي القسمة على k، لولا وجود هذا المتغير).
class MyCircularQueue:
def __init__(self, k):
self.data = [0] * k
self.head = 0
self.tail = 0
self.count = 0
self.k = k
def enQueue(self, value):
if self.isFull(): return False
self.data[self.tail] = value
self.tail = (self.tail + 1) % self.k
self.count += 1
return True
def deQueue(self):
if self.isEmpty(): return False
self.head = (self.head + 1) % self.k
self.count -= 1
return True
def Front(self):
return -1 if self.isEmpty() else self.data[self.head]
def Rear(self):
return -1 if self.isEmpty() else self.data[(self.tail - 1) % self.k]
def isEmpty(self): return self.count == 0
def isFull(self): return self.count == self.k
cq = MyCircularQueue(3)
print(cq.enQueue(1), cq.enQueue(2), cq.enQueue(3)) # True True True
print(cq.enQueue(4)) # False (full)
print(cq.Rear()) # 3
print(cq.isFull()) # True
print(cq.deQueue()) # True
print(cq.enQueue(4)) # Trueأكبر عنصر في النافذة المنزلقة باستخدام deque رتيب
LeetCode 239 «أكبر عنصر في النافذة المنزلقة»: لكل نافذة حجمها k، اعثر على أكبر عنصر. يستغرق الحل بالقوة الغاشمة O(n*k). أما النهج الذي يستغرق O(n) فيستخدم deque رتيبًا تنازليًا يخزّن الفهارس. لكل عنصر جديد: أزل من المقدمة الفهارس الواقعة خارج النافذة؛ وأزل من المؤخرة الفهارس ذات القيم الأصغر (إذ لا يمكنها أن تكون العنصر الأكبر في أي نافذة مستقبلية). وتحتوي المقدمة دائمًا على العنصر الأكبر.
from collections import deque
def maxSlidingWindow(nums, k):
dq = deque() # stores indices, decreasing values
result = []
for i, n in enumerate(nums):
# Remove indices outside window
while dq and dq[0] < i - k + 1:
dq.popleft()
# Remove smaller elements from back
while dq and nums[dq[-1]] < n:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
return result
print(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3))
# [3, 3, 5, 5, 6, 7]لماذا نستخدم deque بدلًا من list للطابور؟
تزيل Python العنصر الأول باستخدام list.pop(0) في O(n)، لأن كل عنصر متبقٍ يجب أن يتحرك موضعًا واحدًا إلى اليسار. ومع n عملية إدراج وn عملية حذف، ينتج عن ذلك O(n²) إجمالًا. أما collections.deque فهو قائمة مزدوجة الارتباط من كتل ثابتة الحجم؛ وتستغرق popleft زمن O(1) لأنها لا تعدّل إلا مؤشرًا واحدًا. وفي BFS على رسم بياني يضم 10^5 عقدة، يكون الفرق بين O(n) وO(n²) هو الفرق بين 100 مللي ثانية و100 ثانية.
import timeit
n = 10000
# Using list (O(n) per popleft)
list_time = timeit.timeit(
stmt='q = list(range(n)); [q.pop(0) for _ in range(n)]',
globals={'n': n}, number=10
)
# Using deque (O(1) per popleft)
from collections import deque
deque_time = timeit.timeit(
stmt='q = deque(range(n)); [q.popleft() for _ in range(n)]',
globals={'n': n, 'deque': deque}, number=10
)
print(f'List: {list_time:.4f}s')
print(f'Deque: {deque_time:.4f}s')
print(f'Speedup: {list_time / deque_time:.1f}x')اجتياز الشجرة الثنائية مستوىً تلو الآخر (LeetCode 102)
LeetCode 102 «اجتياز الشجرة الثنائية مستوىً تلو الآخر»: إعادة جميع قيم العقد مستوىً تلو الآخر. استخدم طابورًا؛ وفي بداية كل مستوى، سجّل حجم الطابور (أي عدد العقد الموجودة في هذا المستوى). أزل هذا العدد بالضبط من العقد، واجمع قيمها وأدرج أبناءها في الطابور. كرر ذلك حتى يفرغ الطابور.
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def levelOrder(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level = []
level_size = len(queue)
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
return result
root = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
print(levelOrder(root)) # [[3], [9, 20], [15, 7]]طابور الأولوية باستخدام heapq
توفر وحدة heapq في Python كومة صغرى (طابور أولوية): يُزال العنصر الأصغر دائمًا أولًا. تضيف heapq.heappush(h, item) عنصرًا بتعقيد O(log n)، وتزيل heapq.heappop(h) أصغر عنصر بالتعقيد نفسه O(log n). في مهام مثل خوارزمية Dijkstra ومسائل top-k، يحل heapq محل الطابور البسيط.
import heapq
pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 1)
heapq.heappush(pq, 3)
heapq.heappush(pq, 2)
print('Min:', heapq.heappop(pq)) # 1
print('Min:', heapq.heappop(pq)) # 2
print('Min:', heapq.heappop(pq)) # 3
# Tasks with priorities
tasks = [(2, 'send email'), (1, 'fix bug'), (3, 'write docs')]
heapq.heapify(tasks)
while tasks:
priority, task = heapq.heappop(tasks)
print(f'Priority {priority}: {task}')النمط التطبيقي: الطابور لمشكلة Word Ladder
LeetCode 127 «Word Ladder»: اعثروا على أقل عدد من استبدالات الحروف المفردة لتحويل كلمة إلى أخرى، باستخدام كلمات القاموس فقط. مثّلوا المسألة كرسم بياني تصل حوافه بين الكلمات التي تختلف في حرف واحد. يعثر BFS على أقصر مسار في هذا الرسم البياني (أي أقل عدد من الخطوات) بتعقيد O(n * L²)، حيث n هو حجم القاموس وL هو طول الكلمة.
from collections import deque
def ladderLength(beginWord, endWord, wordList):
word_set = set(wordList)
if endWord not in word_set:
return 0
queue = deque([(beginWord, 1)])
visited = {beginWord}
while queue:
word, steps = queue.popleft()
for i in range(len(word)):
for ch in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + ch + word[i+1:]
if new_word == endWord:
return steps + 1
if new_word in word_set and new_word not in visited:
visited.add(new_word)
queue.append((new_word, steps + 1))
return 0
print(ladderLength('hit', 'cog', ['hot','dot','dog','lot','log','cog'])) # 5deque بوصفه طابورًا مزدوج الطرفين
تُعدّ collections.deque طابورًا مزدوج الطرفين (deque): يمكنكم الإضافة والحذف بكفاءة من كلا الطرفين. استخدموا الطريقتين appendleft وpopleft للطرف الأمامي، والطريقتين append وpop للطرف الخلفي. يتيح ذلك استخدام deque بوصفه طابورًا وفق FIFO (appendright + popleft) ومكدسًا وفق LIFO (append + pop). تستخدم خوارزمية القيمة القصوى في النافذة المنزلقة كلا الطرفين: أزيلوا الفهارس القديمة من اليسار، والقيم الأصغر من اليمين.
from collections import deque
dq = deque([3, 4, 5])
dq.appendleft(2) # add to front: [2,3,4,5]
dq.appendleft(1) # add to front: [1,2,3,4,5]
dq.append(6) # add to rear: [1,2,3,4,5,6]
print(dq.popleft()) # 1 (from front)
print(dq.pop()) # 6 (from rear)
print(list(dq)) # [2, 3, 4, 5]الخلاصة: الطابور مقابل deque مقابل heap
اختاروا الأداة المناسبة للمسألة. استخدموا طابورًا بسيطًا (deque) للمعالجة وفق FIFO ولخوارزمية BFS. استخدموا deque رتيبًا عندما تحتاجون إلى القيمة القصوى أو الدنيا في نافذة منزلقة؛ فهو يحافظ على خاصية ثبات الترتيب بإزالة العناصر المهيمن عليها. استخدموا طابور أولوية (heapq) عندما تحتاجون إلى الحد الأدنى أو الأقصى على مستوى جميع العناصر بغض النظر عن ترتيبها، كما في خوارزمية Dijkstra أو مسائل top-k. تُعدّ معرفة الأداة التي ينبغي اختيارها وسبب ذلك مهارة أساسية يختبرها المحاورون.
اختبار سريع
اختبروا مدى فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep من هذا الدرس.
خلاصة الدرس
تعلّمتم في هذا الدرس أن: collections.deque يوفر عمليتي enqueue وdequeue بزمن O(1)، مما يجعله التطبيق الصحيح للطابور في Python، وأن BFS يستخدم طابورًا لمعالجة العقد مستوىً بعد مستوى، والعثور على أقصر المسارات في الرسوم البيانية غير الموزونة، وأن deque رتيبًا تنازليًا يحل مشكلة القيمة القصوى في النافذة المنزلقة بتعقيد O(n) عبر إزالة الفهارس المهيمن عليها. بعد ذلك سنستكشف نمط المكدس الرتيب بالتفصيل.
الأسئلة الشائعة
هل درس «تنفيذ الطابور وDeque» مجاني؟
نعم — نص درس «تنفيذ الطابور وDeque» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «تنفيذ الطابور وDeque»؟
أنشئ طابورًا باستخدام deque في Python، ونفّذ طابورًا دائريًا، وحل مسألة أقصى قيمة في نافذة منزلقة باستخدام deque رتيب تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «تنفيذ الطابور وDeque»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تنفيذ المكدس وتطبيقاته
- تنفيذ الطابور وDeque
- نمط المكدس الرتيب
- المحاكاة المتبادلة للمكدس والطابور