Menu
Coddy logo textTech

Trie (albero dei prefissi)

Ultimo aggiornamento

Un trie (si pronuncia "trai"), o albero dei prefissi, memorizza un insieme di stringhe in base ai loro caratteri: ogni arco è etichettato con un carattere e un cammino dalla radice compone un prefisso. Le parole che condividono un prefisso condividono gli stessi nodi, quindi "car", "card" e "care" riusano tutte il cammino c-a-r. Premi Play qui sopra per vedere le parole inserite un carattere alla volta, con una diramazione solo dove differiscono.

Siccome la ricerca attraversa un nodo per carattere, cercare una parola di lunghezza m richiede tempo O(m) indipendentemente da quante parole contenga il trie. Per questo i trie sono ideali per l'autocompletamento, il controllo ortografico e la ricerca per prefisso.

Complessità temporale e spaziale

OperazioneComplessitàNote
InserimentoO(m)m = lunghezza della parola
RicercaO(m)Un passo per carattere
Query per prefissoO(m)Scendi fino al nodo del prefisso
SpazioO(total chars)I prefissi condivisi vengono memorizzati una sola volta

Passo dopo passo (inserimento)

PassoCosa succede
1Parti dal nodo radice.
2Per ogni carattere della parola, cerca un arco figlio corrispondente.
3Se esiste, seguilo (riusando il prefisso condiviso).
4Altrimenti, crea un nuovo nodo figlio per quel carattere.
5Dopo l'ultimo carattere, segna quel nodo come fine parola.

Esempio svolto

Inserimento di ["car", "card", "care"] in un trie vuoto:

PassoStrutturaAzione
Inserisci carroot → c → a → r✓Non esistono figli corrispondenti, quindi crea c, a, r e segna r come fine parola.
Inserisci cardroot → c → a → r✓ → d✓Riusa il cammino esistente c-a-r, poi crea un nuovo figlio d e segnalo come fine parola.
Inserisci careroot → c → a → r✓ → {d✓, e✓}Riusa c-a-r, crea una diramazione da r con un nuovo figlio e e segna e come fine parola.
Cerca careroot → c → a → r → e✓Percorri c, a, r, e; il nodo finale è segnato come fine parola, quindi care è presente.
Cerca caroot → c → aIl cammino esiste ma a non è segnato come fine parola, quindi ca è un prefisso ma non una parola memorizzata.

Quando usare un trie

Usalo quandoEvitalo quando
Ti servono query per prefisso o autocompletamento su un insieme di stringhe.Ti servono solo ricerche per chiave intera: una tabella hash è più veloce e leggera.
Molte parole memorizzate condividono prefissi comuni, quindi i nodi vengono riusati.Le chiavi sono lunghe e raramente si sovrappongono, sprecando un nodo per carattere.
Vuoi ottenere le chiavi in ordine tramite una visita.La memoria è scarsa: i puntatori ai figli di ogni nodo aggiungono un overhead considerevole.
Il costo della ricerca deve dipendere dalla lunghezza della chiave, non dalla dimensione dei dati.L'alfabeto è enorme (per esempio tutto Unicode) e i figli sono memorizzati in modo denso.

Codice Trie (Prefix Tree)

Un'implementazione di Trie (Prefix Tree) pulita ed eseguibile in Python, JavaScript, Java, C++, C. Scegli un linguaggio, copia il codice o aprilo già caricato nel Playground di Coddy.

Codice Trie (Prefix Tree) in 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"))
Esegui questo codice nel playground Python

Domande frequenti sul trie

A cosa serve un trie?
I trie alimentano le funzioni basate sui prefissi: autocompletamento e suggerimenti di ricerca, correttori ortografici, tabelle di routing IP e ricerche nei dizionari. Ovunque ti serva rispondere in fretta a "qualche parola memorizzata inizia con questo prefisso?", un trie dà il meglio di sé.
Qual è la complessità di un trie?
Inserimento, ricerca e query per prefisso richiedono tutti tempo O(m), dove m è la lunghezza della parola o del prefisso, indipendentemente da quante parole siano memorizzate. Il prezzo è la memoria: un trie può occupare molto spazio, anche se i prefissi condivisi vengono memorizzati una sola volta.
Che differenza c'è tra un trie e una tabella hash?
Una tabella hash offre una ricerca media in O(1) sulle chiavi intere ma non sa rispondere alle query per prefisso. Un trie è un po' più lento per singola ricerca ma supporta in modo naturale la ricerca per prefisso, la visita ordinata e l'autocompletamento, ed è per questo che si preferisce in quei casi.
Quando usare un trie invece di un albero binario di ricerca?
Usa un trie quando le chiavi sono stringhe e ti servono ricerche per prefisso: costa O(m) per operazione sulla lunghezza della chiave, mentre un BST bilanciato costa O(m log n) perché ogni confronto scorre la stringa e i confronti sono log n. Un BST è la scelta migliore quando le chiavi non sono stringhe o quando l'overhead di memoria conta più della velocità sui prefissi.
Come si gestisce la fine di una parola in un trie?
Ogni nodo ha un flag di fine parola che viene impostato solo quando una parola inserita per intero termina lì. Senza di esso non potresti distinguere una parola memorizzata da un semplice prefisso: per esempio, dopo aver inserito card, il nodo di car esiste ma va considerato una parola solo se anche car è stato inserito.
I trie fanno sempre risparmiare memoria condividendo i prefissi?
Non sempre. La condivisione aiuta solo quando molte chiavi si sovrappongono; con chiavi lunghe e diverse un trie può usare molta più memoria di un set hash, perché ogni carattere diventa un nodo a sé con i suoi puntatori ai figli. Se lo spazio è un problema, un radix tree compresso unisce le catene di nodi con un solo figlio per ridurre quell'overhead.
Illustrazione dei linguaggi di programmazione di Coddy

Padroneggia gli algoritmi con Coddy

INIZIA