المكدس (Stack)
آخر تحديث
المكدس مجموعة لها طرف مفتوح واحد فقط. تضيف قيمة بدفعها إلى القمة، وتزيل قيمة بسحب القمة مرة أخرى، فتكون آخر قيمة دخلت هي أول قيمة تخرج دائمًا. هذا هو معنى LIFO، وهو القاعدة كلها: لا سبيل إلى بلوغ المنتصف دون إزالة ما فوقه أولًا. اضغط على تشغيل في الأعلى وشاهد العمود ينمو مع كل دفع وينكمش من الطرف نفسه مع كل سحب.
القيد نفسه هو المقصود. لأن العمليتين لا تلمسان إلا القمة، فكل واحدة منهما O(1) مهما ارتفع المكدس، وهذه القابلية للتنبؤ هي سبب وقوف المكدس تحت هذا القدر الكبير من الحوسبة: مكدس الاستدعاءات الذي يشغّل التعاود، وسجل التراجع في المحرر، ومطابقة الأقواس في المحلّل، والمكدس الصريح الذي يحوّل البحث بالعمق أولاً التعاودي إلى حلقة. بدّل طرف الإزالة وستحصل على الطابور بدلًا منه.
تعقيد الوقت والمساحة
للمكدس القياسي المبني على مصفوفة أو على قائمة مترابطة:
| العملية | التعقيد | ملاحظات |
|---|---|---|
| الدفع (push) | O(1) | مُستهلَكة عند O(1) على مصفوفة ديناميكية تُعيد التحجيم من حين لآخر. |
| السحب (pop) | O(1) | دائمًا عنصر القمة، فلا حاجة إلى أي إزاحة. |
| الاطّلاع على القمة (peek) | O(1) | قراءة القمة دون إزالتها. |
| البحث | O(n) | ليس هذا ما وُجد المكدس له: عليك السحب نزولًا حتى تصل. |
| المساحة | O(n) | خانة واحدة لكل قيمة مخزَّنة. |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | يبدأ المكدس فارغًا، والقمة لا تشير إلى شيء. |
| 2 | يكتب الدفع القيمة في موضع القمة ويحرّك القمة درجة واحدة للأعلى. |
| 3 | كل دفع تالٍ يستقر مباشرة فوق القيمة السابقة. |
| 4 | يقرأ السحب القيمة عند القمة، ثم يحرّك القمة درجة واحدة للأسفل. |
| 5 | القيمة العائدة هي دائمًا آخر قيمة جرى دفعها. |
| 6 | سحب مكدس فارغ خطأ يسمى نقص المكدس (stack underflow)، ولهذا تتحقق الشيفرة الحقيقية من is_empty() أولًا. |
مثال محلول
دفع 3 و7 و5 ثم تفريغ المكدس:
| العملية | المكدس (من القاع إلى القمة) | ما يعيده |
|---|---|---|
push(3) | [3] | لا شيء |
push(7) | [3, 7] | لا شيء |
push(5) | [3, 7, 5] | لا شيء |
pop() | [3, 7] | 5، أحدث قيمة |
pop() | [3] | 7 |
pop() | [] | 3، أقدم قيمة، وتأتي أخيرًا |
متى تستخدم المكدس
| استخدمه عندما | تجنّبه عندما |
|---|---|
| تحتاج إلى أحدث عنصر أولًا: التراجع، وأزرار الرجوع، ومطابقة الأقواس | تحتاج إلى أقدم عنصر أولًا، وهذا عمل الطابور |
| تحوّل خوارزمية تعاودية إلى أخرى تكرارية | تحتاج إلى البحث أو الفهرسة في منتصف البيانات |
| تحلّل بنية متداخلة مثل التعابير أو JSON أو HTML | يحتاج قرّاء كثيرون إلى وصول عشوائي، فالمصفوفة أو الخريطة أنسب |
تريد إدراجًا وإزالة مضمونين بـ O(1) دون إعادة موازنة | تحتاج إلى إبقاء البيانات مرتبة، وهو ما تمنحه الكومة أو الشجرة |
كود Stack
تنفيذ نظيف وقابل للتشغيل لخوارزمية Stack بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Stack بلغة Python
1stack = []2
3# Push three values onto the top4for value in [3, 7, 5]:5 stack.append(value)6 print(f"push {value} -> {stack}")7
8# Pop them back off: last in, first out9while stack:10 value = stack.pop()11 print(f"pop {value} -> {stack}")12
13print("empty:", len(stack) == 0)كود Stack بلغة JavaScript
1const stack = [];2
3// Push three values onto the top4for (const value of [3, 7, 5]) {5 stack.push(value);6 console.log(`push ${value} ->`, stack);7}8
9// Pop them back off: last in, first out10while (stack.length > 0) {11 const value = stack.pop();12 console.log(`pop ${value} ->`, stack);13}14
15console.log('empty:', stack.length === 0);كود Stack بلغة Java
1import java.util.ArrayDeque;2import java.util.Deque;3
4public class Main {5 public static void main(String[] args) {6 Deque<Integer> stack = new ArrayDeque<>();7
8 // Push three values onto the top9 for (int value : new int[] {3, 7, 5}) {10 stack.push(value);11 System.out.println("push " + value + " -> " + stack);12 }13
14 // Pop them back off: last in, first out15 while (!stack.isEmpty()) {16 int value = stack.pop();17 System.out.println("pop " + value + " -> " + stack);18 }19
20 System.out.println("empty: " + stack.isEmpty());21 }22}كود Stack بلغة C++
1#include <iostream>2#include <stack>3
4int main() {5 std::stack<int> stack;6
7 // Push three values onto the top8 for (int value : {3, 7, 5}) {9 stack.push(value);10 std::cout << "push " << value << " -> size " << stack.size() << "\n";11 }12
13 // Pop them back off: last in, first out14 while (!stack.empty()) {15 int value = stack.top();16 stack.pop();17 std::cout << "pop " << value << " -> size " << stack.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << stack.empty() << "\n";21 return 0;22}كود Stack بلغة C
1#include <stdio.h>2
3#define CAP 164
5int stack[CAP];6int top = 0; /* index of the next free slot */7
8int main(void) {9 int values[3] = {3, 7, 5};10
11 /* Push three values onto the top */12 for (int i = 0; i < 3; i++) {13 stack[top++] = values[i];14 printf("push %d -> size %d\n", values[i], top);15 }16
17 /* Pop them back off: last in, first out */18 while (top > 0) {19 int value = stack[--top];20 printf("pop %d -> size %d\n", value, top);21 }22
23 printf("empty: %d\n", top == 0);24 return 0;25}الأسئلة الشائعة حول المكدس
ماذا تعني LIFO؟
ما الفرق بين المكدس والطابور؟
O(1)، لكن المكدس يزيل من الطرف نفسه (LIFO) بينما يزيل الطابور من الطرف الآخر (FIFO). وكل ما عدا ذلك، بما في ذلك جدول التعقيد أعلاه، متطابق.ما هي عمليات المكدس الأساسية؟
push قيمة إلى القمة، وتزيل pop قيمة القمة وتعيدها، وتقرأ peek (وتسمى أحيانًا top) القمة دون إزالتها، ويخبرك is_empty بما إذا كان قد بقي شيء. العمليات الأربع كلها O(1).ما هو فيضان المكدس؟
كيف يُنفَّذ المكدس؟
O(1) مُستهلَكة وصديقة لذاكرة التخزين المؤقت: هكذا تعمل list في بايثون و ArrayDeque في جافا. القائمة المتصلة تضيف وتزيل من الرأس، وذلك O(1) في أسوأ الحالات دون إعادة تحجيم، لكنها تكلف مؤشرًا لكل عنصر. وفي C++ std::stack هو مُحوِّل يعمل افتراضيًا فوق std::deque، وهي مصفوفة مجزأة، ويقبل حاوية أخرى إذا مررتها.