Menu
Coddy logo textTech

Trie (עץ תחיליות)

עודכן לאחרונה

Trie (נהגה "טריי"), או עץ תחיליות (prefix tree), שומר קבוצת מחרוזות לפי התווים שלהן: כל קשת מסומנת בתו, ומסלול מהשורש מאיית תחילית. מילים שחולקות תחילית חולקות את אותם צמתים, כך ש-"car", "card" ו-"care" משתמשות כולן באותו מסלול c-a-r. לחצו על הפעלה למעלה כדי לראות מילים שנכנסות תו אחר תו ומתפצלות רק במקום שבו הן שונות.

מכיוון שחיפוש עובר צומת אחד לכל תו, חיפוש מילה באורך m לוקח זמן O(m) בלי קשר למספר המילים שב-trie. זה הופך את ה-trie לאידיאלי להשלמה אוטומטית, לבדיקת איות ולחיפוש לפי תחילית.

סיבוכיות זמן וזיכרון

פעולהסיבוכיותהערות
הכנסהO(m)m = אורך המילה
חיפושO(m)צעד אחד לכל תו
שאילתת תחיליתO(m)הליכה עד לצומת של התחילית
זיכרוןO(total chars)תחיליות משותפות נשמרות פעם אחת

צעד אחר צעד (הכנסה)

צעדמה קורה
1מתחילים בצומת השורש.
2לכל תו במילה, מחפשים קשת בן תואמת.
3אם היא קיימת, הולכים בה (ומשתמשים שוב בתחילית המשותפת).
4אם לא, יוצרים צומת בן חדש עבור התו הזה.
5אחרי התו האחרון, מסמנים את הצומת הזה כסוף מילה.

דוגמה מפורטת

הכנסת ["car", "card", "care"] ל-trie ריק:

צעדמבנהפעולה
הכנסת carroot → c → a → r✓אין בנים תואמים, לכן יוצרים את c, a, r ומסמנים את r כסוף מילה.
הכנסת cardroot → c → a → r✓ → d✓משתמשים שוב במסלול הקיים c-a-r, ואז יוצרים בן חדש אחד d ומסמנים אותו כסוף מילה.
הכנסת careroot → c → a → r✓ → {d✓, e✓}משתמשים שוב ב-c-a-r, מתפצלים מ-r עם בן חדש e, ומסמנים את e כסוף מילה.
חיפוש careroot → c → a → r → e✓הולכים דרך c, a, r, e; הצומת האחרון מסומן כסוף מילה, ולכן care קיימת.
חיפוש caroot → c → aהמסלול קיים אבל a לא מסומן כסוף מילה, ולכן ca היא תחילית ולא מילה שמורה.

מתי להשתמש ב-trie

כדאי כאשרעדיף להימנע כאשר
צריך שאילתות תחילית או השלמה אוטומטית על קבוצת מחרוזות.צריך רק חיפוש של מפתחות שלמים: טבלת גיבוב מהירה וקלה יותר.
מילים רבות שמורות חולקות תחיליות משותפות, כך שצמתים מנוצלים שוב.המפתחות ארוכים ורק לעיתים רחוקות חופפים, וזה מבזבז צומת לכל תו.
רוצים לקבל את המפתחות בסדר ממוין באמצעות מעבר על העץ.הזיכרון מוגבל: מצביעי הבנים בכל צומת מוסיפים תקורה משמעותית.
עלות החיפוש צריכה להיות תלויה באורך המפתח ולא בגודל אוסף הנתונים.האלפבית ענק (למשל כל Unicode) והבנים נשמרים בצורה צפופה.

קוד Trie (Prefix Tree)

מימוש נקי של Trie (Prefix Tree) שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.

קוד Trie (Prefix Tree) ב-Python

Python
1class TrieNode:2    def __init__(self):3        self.children = {}4        self.is_word = False5
6
7class Trie:8    def __init__(self):9        self.root = TrieNode()10
11    def insert(self, word):12        node = self.root13        for ch in word:14            node = node.children.setdefault(ch, TrieNode())15        node.is_word = True16
17    def search(self, word):18        node = self._walk(word)19        return node is not None and node.is_word20
21    def starts_with(self, prefix):22        return self._walk(prefix) is not None23
24    def _walk(self, s):25        node = self.root26        for ch in s:27            if ch not in node.children:28                return None29            node = node.children[ch]30        return node31
32
33trie = Trie()34for word in ["car", "card", "care", "dog"]:35    trie.insert(word)36
37print("search(card):     ", trie.search("card"))38print("search(ca):       ", trie.search("ca"))39print("starts_with(ca):  ", trie.starts_with("ca"))40print("starts_with(do):  ", trie.starts_with("do"))41print("starts_with(cat): ", trie.starts_with("cat"))
להריץ את הקוד הזה בעורך ה-Python אונליין

שאלות נפוצות על trie

למה משמש trie?
Trie מפעיל פיצ'רים שמבוססים על תחיליות: השלמה אוטומטית והצעות חיפוש, בודקי איות, טבלאות ניתוב IP וחיפוש במילון. בכל מקום שבו צריך שאילתות מהירות מסוג "האם יש מילה שמורה שמתחילה בתחילית הזו?", trie מצטיין.
מהי סיבוכיות הזמן של trie?
הכנסה, חיפוש ושאילתות תחילית רצים כולם בזמן O(m), כאשר m הוא אורך המילה או התחילית, בלי תלות במספר המילים השמורות. המחיר הוא זיכרון: trie יכול לצרוך הרבה מקום, אף שתחיליות משותפות נשמרות פעם אחת בלבד.
מה ההבדל בין trie לטבלת גיבוב?
טבלת גיבוב מציעה חיפוש של מפתחות שלמים ב-O(1) בממוצע, אבל לא יכולה לענות על שאילתות תחילית. Trie איטי מעט יותר בכל חיפוש, אבל תומך באופן טבעי בחיפוש לפי תחילית, במעבר ממוין ובהשלמה אוטומטית, ולכן מעדיפים אותו במקרים האלה.
מתי להשתמש ב-trie במקום בעץ חיפוש בינארי?
השתמשו ב-trie כשהמפתחות הם מחרוזות וצריך חיפושי תחילית: הוא רץ ב-O(m) לכל פעולה לפי אורך המפתח, בעוד עץ חיפוש בינארי מאוזן עולה O(m log n), כי כל השוואה סורקת את המחרוזת ויש log n השוואות כאלה. עץ חיפוש בינארי הוא הבחירה הטובה יותר כשהמפתחות אינם מחרוזות או כשתקורת הזיכרון חשובה יותר ממהירות התחיליות.
איך מטפלים בסוף מילה ב-trie?
כל צומת מחזיק דגל סוף מילה שמודלק רק כשמילה שלמה שהוכנסה מסתיימת בו. בלעדיו אי אפשר להבחין בין מילה שמורה לבין תחילית בלבד: למשל אחרי הכנסת card, הצומת של car קיים, אבל צריך לדווח עליו כמילה רק אם גם car הוכנסה.
האם trie תמיד חוסך זיכרון בזכות תחיליות משותפות?
לא תמיד. השיתוף עוזר רק כשמפתחות רבים חופפים; עם מפתחות ארוכים ושונים, trie יכול לצרוך הרבה יותר זיכרון מקבוצת גיבוב, כי כל תו הופך לצומת נפרד עם מצביעי בנים. אם הזיכרון הוא הדאגה, עץ radix דחוס ממזג שרשראות של בן יחיד כדי לצמצם את התקורה הזו.
איור של שפות התכנות ב-Coddy

לשלוט באלגוריתמים עם Coddy

להתחיל