مستوى المقال: متوسط
الافتراض إنك تعرف الأساسيات: يعني إيه
dictفي بايثون أوHashMapفي جافا، ويعني إيهO(1)وO(n). لو لسه مبتدئ تمامًا، المثال الأول تحت هيبسّط لك الفكرة قبل الشرح العلمي.
الـ Hash Table: ليه بيتحوّل من O(1) إلى O(n)؟
الـ dict في بايثون والـ HashMap في جافا بيوصلوا لأي عنصر في خطوة واحدة تقريبًا. تحت ضغط معيّن، نفس البنية بتبقى بطيئة لدرجة توقّع سيرفرك. هنا هتشوف إمتى بالظبط بيحصل التحوّل من O(1) إلى O(n)، وإزاي تمنعه بكود تقيسه بنفسك.
المشكلة باختصار
كل الناس بتفتكر إن الـ Hash Table بحث فيها ثابت O(1) على طول. ده صح في المتوسط بس. الطريقة دي بتفشل في حالتين: hash ضعيف بيكدّس المفاتيح في نفس المكان، أو معامل تحميل عالي جدًا. في الحالتين البحث بيرجع خطّي، والفرق مش تجميلي — ممكن يبقى آلاف المرات.
المثال المبسّط: جدار صناديق البريد
تخيّل جدار فيه 100 صندوق بريد مرقّم. عايز توصّل خطاب لـ"أحمد". بدل ما تدوّر صندوق ورا صندوق، عندك قاعدة بسيطة: تجمع أرقام حروف الاسم وتقسّم على 100، والباقي هو رقم الصندوق. اسم "أحمد" بيروح دايمًا للصندوق رقم 37. فتوصل له في خطوة واحدة، مش 100 خطوة. ده بالظبط اللي بيعمله الـ Hash Table.
المشكلة تبدأ لما "أحمد" و"محمد" و"سارة" كلهم يطلع لهم نفس الرقم 37. دلوقتي الصندوق 37 فيه كومة خطابات، ولازم تقلّبها واحد واحد لحد ما تلاقي بتاع أحمد. ده اسمه تصادم (collision)، ولو كل الأسماء كدّست في صندوق واحد، رجعت لتقليب الكومة كلها — يعني O(n).
الشرح العلمي: hash function و buckets
الـ Hash Table هي مصفوفة من الخانات (buckets). لما تحط مفتاح، بتمرّره على دالة تجزئة (hash function) بتحوّله لرقم، وبتاخد باقي القسمة على حجم المصفوفة عشان توصل لرقم الخانة: index = hash(key) % capacity. الوصول للمصفوفة بالـ index ثابت، فالبحث بيبقى O(1) في المتوسط طول ما التوزيع شبه منتظم.
الافتراض المخفي هنا: إن دالة الـ hash بتوزّع المفاتيح بالتساوي، وإن المفاتيح مش تحت تحكّم مهاجم. لو الافتراض ده اتكسر، الضمان بتاع O(1) بيتكسر معاه.
قيس الفرق بنفسك (كود شغّال)
الكود ده بيبني قاموسين في بايثون: واحد بمفاتيح كل واحد ليه hash مختلف (توزيع كويس)، والتاني بمفاتيح كلها بترجّع نفس الـ hash (تصادم كامل). بعدين بيقيس زمن بحثة واحدة في كل قاموس.