ماذا يعني dynamic في الخوارزميات وهياكل البيانات؟ شرح مبسّط ودقيق
كلمة dynamic (ديناميكي) في علوم الحاسب لا تعني «ذكي» أو «قادر على التعلّم» كما يُشاع أحياناً، بل لها معنى تقني واحد بسيط: كل ما يحدث أو يتحدّد وقت التشغيل (runtime) بدل أن يكون ثابتاً ومعروفاً مسبقاً. أي أنّ عكس «dynamic» هو static (ثابت): الثابت يُقرَّر وقت كتابة الكود أو ترجمته، والديناميكي يُقرَّر أثناء عمل البرنامج فعلياً. من هذا المعنى الجذري تتفرّع ثلاثة استخدامات شائعة يخلط بينها كثيرون: هياكل البيانات الديناميكية، والبرمجة الديناميكية، والتخصيص الديناميكي للذاكرة. دعنا نفصّلها.
المعنى الجذري: ثابت مقابل ديناميكي
تخيّل قاعة سينما بعدد كراسي محدّد لا يتغيّر: هذا «ثابت». الآن تخيّل صالة أفراح تضيف كراسي وتزيلها حسب عدد الحضور: هذا «ديناميكي». في البرمجة، الثابت يعني أنّ الحجم أو النوع أو المسار محدَّد سلفاً ولا يتغيّر، بينما الديناميكي يعني أنّ البرنامج يقرّره ويعدّله لحظياً حسب المعطيات. احتفظ بهذه الصورة، فهي تفسّر كل الاستخدامات التالية.
أولاً: هياكل البيانات الديناميكية
هيكل البيانات الديناميكي هو الذي يستطيع تغيير حجمه أثناء التشغيل — يكبر عند إضافة عناصر ويصغر عند حذفها — لأنه يحجز الذاكرة من «الكومة (heap)» لحظة الحاجة.
- أمثلة ديناميكية: القوائم المترابطة (Linked Lists)، والأشجار (Trees)، وجداول التجزئة (Hash Tables)، والمصفوفة الديناميكية مثل
ArrayListفي جافا وvectorفي ++C وlistفي بايثون. - المقابل الثابت: المصفوفة التقليدية ذات الحجم المحدَّد مسبقاً (مثل
int arr[100])، التي لا يمكن توسيعها بعد إنشائها.
الفائدة الحقيقية ليست «الذكاء» بل المرونة: تتعامل مع كمية بيانات لا تعرف حجمها مقدماً دون إهدار ذاكرة أو نفادها. لكن انتبه: هذه المرونة لها ثمن، فالوصول إلى عنصر في قائمة مترابطة أبطأ من المصفوفة، والتخصيص المتكرر للذاكرة يضيف حِملاً. الديناميكي ليس دائماً «الأفضل»، بل الأنسب حين يكون الحجم غير معروف أو متغيّراً.
ثانياً: البرمجة الديناميكية (Dynamic Programming)
هنا يقع أشهر خلط. البرمجة الديناميكية أسلوب لحل المسائل، وليست نوعاً من هياكل البيانات، ولا علاقة لاسمها بمعنى «المتغيّر» إطلاقاً. الفكرة: تقسيم المسألة الكبيرة إلى مسائل فرعية متكرّرة، وحلّ كلٍّ منها مرة واحدة فقط ثم تخزين النتيجة لإعادة استخدامها بدل إعادة حسابها.
المثال الكلاسيكي أرقام فيبوناتشي: الحساب التقليدي بالاستدعاء الذاتي يعيد حساب القيم نفسها آلاف المرات (تعقيد أُسّي مكلف جداً)، بينما البرمجة الديناميكية تخزّن كل قيمة محسوبة فينزل التعقيد إلى خطّي O(n). أمثلة أخرى: مسألة حقيبة الظهر (Knapsack)، وأطول سلسلة فرعية مشتركة (LCS)، وإيجاد أقصر مسار في الخرائط.
لتنجح البرمجة الديناميكية تحتاج شرطين: مسائل فرعية متداخلة (تتكرّر)، وبنية مثلى (يُبنى الحل الأمثل الكبير من حلول فرعية مثلى). تُنفَّذ بطريقتين: التخزين المؤقت (Memoization) من الأعلى للأسفل، أو الجدولة (Tabulation) من الأسفل للأعلى.
تصحيح خطأ شائع: لماذا سُمّيت «ديناميكية» إذاً؟ لأسباب تاريخية بحتة؛ اختار العالم ريتشارد بيلمان الاسم في الخمسينيات لأنه بدا جذاباً وحيادياً أمام جهة تموِّل بحثه، لا لأنه يصف تقنية «متكيّفة». فلا تربط الاسم بأي معنى للتغيّر أو الذكاء.
ثالثاً: التخصيص الديناميكي للذاكرة
استخدام ثالث ستقابله كثيراً: حجز الذاكرة وقت التشغيل عبر أدوات مثل malloc وfree في لغة C، أو new في ++C وجافا. عكسه التخصيص الثابت (متغيرات بحجم معروف عند الترجمة). وهو الآلية التي تجعل هياكل البيانات الديناميكية ممكنة أصلاً.
جدول: ثابت (static) أم ديناميكي (dynamic)؟
| المعيار | ثابت (Static) | ديناميكي (Dynamic) |
|---|---|---|
| متى يتحدّد؟ | وقت الكتابة/الترجمة | وقت التشغيل |
| الحجم | ثابت لا يتغيّر | يكبر ويصغر حسب الحاجة |
| المرونة | منخفضة | عالية |
| السرعة/الحِمل | أسرع وأخفّ عادةً | حِمل إضافي على الذاكرة |
| مثال | مصفوفة بحجم ثابت | قائمة مترابطة، ArrayList |
نصيحة عملية: حين تقرأ كلمة «dynamic» في أي مقال أو توثيق، اسأل نفسك أولاً: أهي عن هيكل بيانات يكبر ويصغر، أم عن تقنية البرمجة الديناميكية لحل مسألة، أم عن الذاكرة؟ التمييز بينها يمنع أغلب الالتباس، لأن الكلمة نفسها تُستخدم في ثلاثة سياقات مختلفة تماماً.
الأسئلة الشائعة
هل البرمجة الديناميكية نوع من هياكل البيانات الديناميكية؟ لا، وهذا الخلط منتشر. الأولى أسلوب لحل المسائل بتخزين النتائج الجزئية، والثانية بنى تخزين تغيّر حجمها. تجمعهما الكلمة فقط، لا المفهوم.
ما الفرق بين dynamic array وlinked list؟ كلاهما ديناميكي، لكن المصفوفة الديناميكية تخزّن العناصر متجاورة وتضاعف سعتها عند الامتلاء (وصول سريع بالفهرس)، بينما القائمة المترابطة تربط عناصر متفرّقة بمؤشرات (إضافة وحذف أسرع، لكن وصول أبطأ).
هل «dynamic typing» في بايثون له علاقة بهذا؟ هو استخدام رابع للكلمة نفسها بالمعنى الجذري ذاته: نوع المتغيّر يُفحص وقت التشغيل لا وقت الترجمة (عكس اللغات ذات النوع الثابت مثل جافا). المبدأ واحد، والمجال مختلف.
متى أختار الديناميكي على الثابت؟ حين لا تعرف حجم البيانات مسبقاً أو تتوقّع تغيّره. أما إن كان الحجم معروفاً وثابتاً وتريد أقصى أداء، فالثابت أكفأ وأخفّ على الذاكرة.