هذا المقال للمستوى المتوسط — يفترض إن عندك أساسيات Python والمصفوفات وشوية إلمام بـ Big O.
لاقي أعلى 100 سعر من مليون منتج في 18 مللي ثانية بدل 580 مللي ثانية
لو الـ dashboard بتاع متجرك بيرجّع شاشة "أغلى 100 منتج" بياخد نص ثانية أو ثانية على جدول فيه مليون صف، المشكلة مش الـ database ولا الـ index. المشكلة إن الكود بيرتّب مليون عنصر علشان ياخد منهم 100 بس. Heap بيقلب المعادلة: نفس النتيجة في 18 مللي ثانية، بسطر Python واحد.
المشكلة باختصار
الكود الشائع لما تطلب top-N من قائمة كبيرة بيبقى كده:
top_100 = sorted(products, reverse=True)[:100]
الكود ده بيرتّب المليون كله — يعني تكلفة O(n log n) — وبعدين بيرمي 999,900 عنصر منهم. ده شغل اتعمل من غير لازمة. على Apple M2 و CPython 3.12 ده بياخد ≈ 580ms على مليون عنصر. لو بتعمل ده داخل API request، p95 latency بتطير.
تخيّل الموضوع كإنه طوارئ مستشفى
في طوارئ أي مستشفى، اللي بيدخل أول مش اللي وصل أول. اللي بيدخل أول هو الأخطر حالة. الممرّض المسؤول عن الفرز ما بيرتّبش كل المرضى من الأخطر للأخف — ده هيستهلك وقت رهيب لو دخلوا 200 مريض في ساعة. هو بس بيحافظ على قاعدة واحدة: أخطر حالة لازم تبقى على رأس الطابور دايمًا. باقي الترتيب مش مهم. لما يخش مريض جديد، الممرّض بيقارنه بس بحالات قليلة قريبة منه في الترتيب. ده بالظبط Heap.
تعريف علمي دقيق لـ Heap
الـ Heap هي شجرة ثنائية كاملة (Complete Binary Tree) بتحقّق قاعدة واحدة اسمها Heap Property: قيمة كل عقدة لازم تبقى أكبر من قيم أولادها (في Max-Heap) أو أصغر (في Min-Heap). الترتيب الكلي بين العقد مش مضمون — في بس ضمان إن أعلى/أقل قيمة دايمًا في الـ root.
الـ heap بتتمثّل عادةً كـ array، مش pointers. للعقدة في index i:
- الأب في index
(i - 1) // 2 - الابن الشمال في
2*i + 1 - الابن اليمين في
2*i + 2
التكلفة:
- إضافة عنصر: O(log n)
- قراءة أعلى/أقل قيمة (الـ root): O(1)
- حذف الـ root مع إعادة الترتيب: O(log n)
- إيجاد top-k من n عنصر: O(n log k) — ده الكسب الكبير لما k أصغر بكتير من n.