ما معنى Run Time في الخوارزميات وهياكل البيانات؟ شرح مبسّط مع أمثلة

شروحات تقنية

"زمن التشغيل" (Run Time) في مجال الخوارزميات وهياكل البيانات هو المدة التي تستغرقها الخوارزمية حتى تُنهي عملها على مُدخلات معيّنة. لكن الفكرة المهمة التي يغفل عنها الكثيرون: عندما يتحدث المبرمجون عن "زمن تشغيل خوارزمية"، فهم في الغالب لا يقصدون عدد الثواني الفعلية على جهازك، بل يقصدون كيف يكبر هذا الزمن مع كبر حجم البيانات. هذا التمييز هو مفتاح فهم الموضوع كله.

زمن التنفيذ الفعلي مقابل التعقيد الزمني

هنا يقع أشهر خلط في هذا المجال، فلننتبه للفرق بين مصطلحين متقاربين:

  • زمن التنفيذ الفعلي (Execution Time): عدد المللي ثانية التي استغرقها البرنامج فعلياً. هذا الرقم غير ثابت، إذ يتغيّر حسب سرعة المعالج، وحِمل النظام، ولغة البرمجة، بل حتى حسب حالة الذاكرة المؤقتة. لذلك لا يصلح للمقارنة بين خوارزميتين بشكل عادل.
  • التعقيد الزمني (Time Complexity): وصف رياضي مجرّد لمعدّل نمو عدد العمليات مع حجم المُدخلات (n). هذا هو ما نقصده عادةً حين نقول "زمن تشغيل الخوارزمية"، ونعبّر عنه بترميز الأُس الكبير Big O.

باختصار: عندما تقرأ أن خوارزمية "زمن تشغيلها O(n)"، فالمعنى ليس أنها تأخذ n ثانية، بل أن عدد خطواتها يتضاعف تقريباً كلما تضاعف حجم البيانات.

لماذا يهمّ زمن التشغيل؟

على البيانات الصغيرة قد لا تلاحظ فرقاً، فكل الخوارزميات تبدو سريعة. المشكلة تظهر عند التوسّع (Scale). خوارزمية O(n²) قد تُنهي عملها على ألف عنصر في لحظة، لكنها قد تحتاج دقائق أو ساعات على مليون عنصر، بينما تُنجز خوارزمية O(n log n) المهمة نفسها في ثوانٍ. اختيار الخوارزمية المناسبة قد يعني الفرق بين تطبيق يستجيب فوراً وآخر يتجمّد.

أشهر أنواع زمن التشغيل (بترميز Big O)

الترميزالاسممثال عمليالسلوك مع كبر البيانات
O(1)زمن ثابتالوصول إلى عنصر في مصفوفة عبر فهرسهلا يتأثر بحجم البيانات إطلاقاً
O(log n)لوغاريتميالبحث الثنائي في قائمة مرتبةيكبر ببطء شديد
O(n)خطّيالمرور على كل عناصر قائمة غير مرتبةيتضاعف مع تضاعف البيانات
O(n log n)شبه خطّيالفرز الفعّال مثل Merge Sortمقبول جداً حتى للبيانات الكبيرة
O(n²)تربيعيفرز الفقاعة (Bubble Sort)يبطؤ بسرعة، تجنّبه للبيانات الكبيرة
O(2ⁿ)أُسّيبعض حلول القوة الغاشمةيصبح مستحيلاً عملياً بسرعة

القاعدة العامة: كلما نزلت في هذا الجدول ازداد بطء الخوارزمية عند التوسّع، والهدف دائماً أن تبقى في أعلى الجدول قدر الإمكان.

أفضل حالة وأسوأ حالة والحالة المتوسطة

زمن التشغيل نفسه قد يختلف حسب شكل المُدخلات، لذلك نحلّل ثلاث حالات:

  • أفضل حالة (Best Case): أسرع سيناريو ممكن. مثلاً في البحث الخطّي، أن يكون المطلوب هو أول عنصر، فيكون الزمن O(1).
  • أسوأ حالة (Worst Case): أبطأ سيناريو، مثل أن يكون المطلوب في آخر القائمة أو غير موجود أصلاً، فيصبح O(n). وهذه هي الحالة الأهم عملياً لأننا نصمّم الأنظمة لتحمّل الأسوأ.
  • الحالة المتوسطة (Average Case): المتوسط المتوقّع عبر كل المُدخلات المحتملة.

ملاحظة تصحيحية مهمة: هذه الحالات تُوصف باستخدام ترميز Big O نفسه أو ترميزات Ω و Θ، ولا يوجد شيء اسمه "O(B)" أو "O(W)" أو "O(A)"؛ فالحرف داخل الأقواس يمثّل حجم المُدخلات لا اسم الحالة.

كيف نحسب زمن التشغيل؟

لا تحتاج إلى تشغيل الكود بالضرورة، بل تحلّله بصرياً وفق خطوات بسيطة:

  1. حدّد العملية الأساسية المتكرّرة (مقارنة، إسناد، جمع...).
  2. اعدد كم مرة تتكرّر بدلالة حجم المُدخلات n.
  3. الحلقة الواحدة على البيانات تعطي عادةً O(n)، والحلقة داخل حلقة تعطي O(n²).
  4. احذف الثوابت والحدود الأقل أهمية، فمثلاً 3n + 5 تصبح O(n)، لأن Big O يهتم بمعدّل النمو لا بالأرقام الدقيقة.

خطأ شائع يجب تجنّبه

لا تخلط بين زمن التشغيل والمساحة المستهلكة (Space Complexity). خوارزمية قد تكون سريعة زمنياً لكنها تلتهم ذاكرة كبيرة، والعكس صحيح. الأداء الجيد توازن بين الاثنين، وليس السرعة وحدها. كما لا تنخدع بأن O(1) دائماً أسرع من O(n) على أرض الواقع؛ فالثوابت المخفية قد تجعل خوارزمية O(n) بسيطة أسرع فعلياً من خوارزمية O(1) مثقلة، خصوصاً على البيانات الصغيرة.

الأسئلة الشائعة

هل زمن التشغيل هو نفسه سرعة الجهاز؟ لا. سرعة الجهاز تؤثر في الزمن الفعلي بالثواني، أما زمن التشغيل بمعناه التحليلي (Big O) فيصف الخوارزمية نفسها بمعزل عن العتاد.

ما الفرق بين Big O و Big Θ؟ Big O يضع حدّاً أعلى (لن تتجاوزه الخوارزمية)، بينما Θ يصف الحد الدقيق حين يتطابق الحد الأعلى والأدنى. في الاستخدام اليومي يكتفي المبرمجون بـ Big O.

أي تعقيد زمني يُعدّ "جيداً"؟ O(1) و O(log n) و O(n) و O(n log n) تُعتبر كفؤة ومقبولة. أما O(n²) فما فوق فيصبح مشكلة عند كبر البيانات ويستحق إعادة التفكير.

هل أحتاج حفظ كل هذه الترميزات؟ يكفي أن تفهم فكرة النمو وأن تميّز الحلقات المتداخلة. مع الممارسة سيصبح تقدير التعقيد الزمني أمراً بديهياً أثناء قراءة الكود.