لو search box عندك بيستعلم على PostgreSQL بـ LIKE 'pro%' على جدول 5 ملايين كلمة، الـ query بياخد 820 مللي ثانية. Trie في الذاكرة بينزّل نفس البحث لـ 78 ميكروثانية. ده 10500x أسرع، ومش محتاج Redis ولا ElasticSearch.
Trie: الـ Data Structure اللي وراء autocomplete في كل مكان
المشكلة باختصار
كل مرة بتكتب حرف في Google Search، Amazon، أو في autocomplete بتاع VS Code، الاقتراحات بتظهر في أقل من 100 ميكروثانية. لو السيستم بيدوّر خطّيًا في 50 مليون كلمة في كل ضغطة keystroke، ده مستحيل فيزيائيًا. اللي بيحصل فعلاً إن في Data Structure اسمها Trie (تنطق "تراي") بتعمل البحث بطول الكلمة، مش حجم القاموس.
الفرق هنا جوهري: Hash Map بيلاقي مفتاح كامل في O(1). Trie بيلاقي كل المفاتيح اللي بتبدأ بـ prefix معيّن في O(L) حيث L طول الـ prefix. ده اللي بيخلّي autocomplete ممكن أصلاً.
مثال للمبتدئ: خزانة الكروت في المكتبة
تخيّل عندك خزانة فيها 5 ملايين كرت، كل كرت عليه كلمة. لو حد سألك: "إيه الكلمات اللي بتبدأ بـ pro؟"، أمامك خياران:
- تفتّش الـ 5 ملايين كرت كرت كرت. ده ممكن ياخد ساعتين، ومستحيل في مللي ثانية.
- تنظّم الخزانة قبل كده. درج لكل حرف أول، جوّاه درج فرعي لكل حرف ثاني، وهكذا. لما حد يقول "pro"، بتفتح درج P، جوّاه درج R، جوّاه درج O، وهتلاقي كل اللي بيبدأ بـ "pro" مجمّعين في مكان واحد.
الخيار الثاني ده بالظبط Trie. كل عقدة في الشجرة بتمثّل حرف، والمسار من الجذر للورقة بيمثّل كلمة كاملة. الكلمات اللي بتشترك في prefix بتشترك في نفس المسار من البداية.
التعريف العلمي الدقيق
Trie (الاسم مأخوذ من كلمة retrieval، صاغها Edward Fredkin سنة 1960) هي شجرة جذرية (rooted tree) كل عقدة فيها بتخزّن:
- children map: خريطة من حرف لعقدة ابنة. غالبًا dict في Python أو HashMap في Java.
- is_end flag: علم منطقي بيقول إن العقدة دي نهاية كلمة صحيحة. ده مهم لأن "car" و "card" بيشتركوا في 3 حروف، ولازم نميّز نهاية كل واحدة.
- payload اختياري: قيمة مرتبطة بالكلمة (تكرار الاستخدام، priority، ID المنتج، إلخ).
التعقيد الزمني للبحث: O(L) حيث L طول الكلمة، مش O(N) حيث N حجم القاموس. ده بيخلّي البحث ثابت تقريبًا حتى لو القاموس كبر من مليون لمليار، طول ما طول الكلمات نفسها مش بيتغيّر.