Classe TrieNode: inserimento e ricerca
Costruisca un TrieNode con un dizionario children e un flag is_end, implementi insert e exact-search e analizzi il tempo O(m) per operazione, dove m è la lunghezza della parola.
Classe TrieNode: inserimento e ricerca è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.
Che cos'è un Trie?
Un Trie (albero dei prefissi) è una struttura dati ad albero in cui ogni nodo rappresenta un carattere. Le parole vengono memorizzate concatenando i caratteri dalla radice alla foglia. La radice rappresenta una stringa vuota. Ogni percorso dalla radice a un nodo is_end = True forma una parola memorizzata. I Trie sono ideali per le query basate sui prefissi, come il completamento automatico, il controllo ortografico e il routing IP, per i quali offrono prestazioni migliori delle mappe hash.
Progettazione della classe TrieNode
Un TrieNode ha due campi: children — un dizionario che associa i caratteri ai TrieNode figli — e is_end — un booleano che indica se questo nodo è la fine di una parola memorizzata. L'utilizzo di un dizionario, invece di un array fisso di 26 caratteri, generalizza la struttura a qualsiasi insieme di caratteri e consente di risparmiare memoria nei Trie sparsi. Ogni nodo del Trie rappresenta esattamente una posizione carattere nelle parole sottostanti.
class TrieNode:
def __init__(self):
self.children = {} # char -> TrieNode
self.is_end = False # True if a word ends here
class Trie:
def __init__(self):
self.root = TrieNode()
def __repr__(self):
return f'Trie(root with {len(self.root.children)} children)'
t = Trie()
print(t) # Trie(root with 0 children)Operazione di inserimento
Per inserire una parola, si percorra il Trie dalla radice, creando un nuovo TrieNode per ogni carattere che non esiste già nei children del nodo corrente. Dopo aver elaborato tutti i caratteri, si imposti is_end = True sul nodo finale. L'inserimento di 'apple' e 'app' crea la catena a→p→p→l→e (is_end=True per 'apple'), con la p in posizione 3 contrassegnata anch'essa come is_end=True per 'app'.
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
t = Trie()
t.insert('apple')
t.insert('app')
print('Inserted apple and app')
print('app is_end:', t.root.children['a'].children['p'].children['p'].is_end)Operazione di ricerca
Per cercare una parola esatta, si percorra il Trie seguendo ogni carattere. Se un carattere manca nei children del nodo corrente, si restituisca False. Se vengono trovati tutti i caratteri, si restituisca node.is_end — True solo se una parola termina esattamente in quel punto, non se esiste soltanto un prefisso. Questa distinzione tra «esiste il prefisso» ed «esiste la parola esatta» è fondamentale e viene spesso verificata.
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for c in word:
if c not in node.children:
node.children[c] = TrieNode()
node = node.children[c]
node.is_end = True
def search(self, word):
node = self.root
for c in word:
if c not in node.children:
return False
node = node.children[c]
return node.is_end # must be a complete word
t = Trie()
t.insert('apple')
print(t.search('apple')) # True
print(t.search('app')) # False (app not inserted)
print(t.search('orange')) # FalseStarts-With (ricerca per prefisso)
Il metodo starts_with verifica se una parola inserita ha il prefisso indicato. Segue lo stesso percorso della ricerca, ma invece di verificare is_end restituisce True non appena tutti i caratteri del prefisso sono stati seguiti correttamente: ciò significa che il percorso del prefisso esiste nel Trie.
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for c in word:
if c not in node.children:
node.children[c] = TrieNode()
node = node.children[c]
node.is_end = True
def search(self, word):
node = self.root
for c in word:
if c not in node.children: return False
node = node.children[c]
return node.is_end
def starts_with(self, prefix):
node = self.root
for c in prefix:
if c not in node.children: return False
node = node.children[c]
return True # prefix path exists
t = Trie()
t.insert('apple')
print(t.starts_with('app')) # True
print(t.starts_with('ape')) # False
print(t.search('app')) # False (not inserted)Complessità temporale e spaziale
Ogni operazione sul Trie (insert, search, starts_with) richiede tempo O(m), dove m è la lunghezza della parola: si percorrono al massimo m nodi. Spazio: O(ALPHABET_SIZE × N × M), dove N è il numero di parole e M è la lunghezza media delle parole. Nella pratica, i prefissi condivisi riducono notevolmente lo spazio. Un dizionario children basato su una mappa hash utilizza meno spazio di un array fisso di 26 caratteri per i Trie sparsi, al costo di un overhead costante leggermente maggiore per ogni ricerca.
Utilizzare un array invece di un dict
Se si utilizzano solo lettere inglesi minuscole, si impieghi un array di dimensione fissa children = [None] * 26, usando come indice ord(c) - ord('a'). Questa soluzione è più veloce, perché la ricerca di un figlio è O(1) rispetto a una mappa hash, e ha una disposizione prevedibile in memoria. Si utilizzi la versione con dict quando l'insieme di caratteri è grande o sconosciuto, ad esempio con Unicode, e la versione con array nei problemi da competizione che prevedono esclusivamente lettere minuscole.
class TrieNodeArray:
def __init__(self):
self.children = [None] * 26
self.is_end = False
class TrieArray:
def __init__(self):
self.root = TrieNodeArray()
def insert(self, word):
node = self.root
for c in word:
idx = ord(c) - ord('a')
if node.children[idx] is None:
node.children[idx] = TrieNodeArray()
node = node.children[idx]
node.is_end = True
def search(self, word):
node = self.root
for c in word:
idx = ord(c) - ord('a')
if node.children[idx] is None: return False
node = node.children[idx]
return node.is_end
t = TrieArray()
t.insert('cat')
print(t.search('cat')) # True
print(t.search('car')) # FalseOperazione di eliminazione
L'eliminazione da un Trie deve gestire tre casi: (1) parola assente — non si esegue alcuna operazione; (2) parola presente ma prefisso di un'altra parola — si azzera soltanto is_end; (3) parola presente e non prefisso — si eliminano i nodi dal basso verso l'alto, fermandosi quando un nodo ha altri figli o rappresenta la fine di un'altra parola. L'eliminazione viene verificata raramente nei colloqui, ma è utile conoscerla a livello concettuale.
Contare le parole con un prefisso
Si aggiunga a ogni nodo un campo count, incrementato ogni volta che un inserimento attraversa il nodo. Per contare le parole con un determinato prefisso, si percorra il Trie fino al nodo finale del prefisso e se ne restituisca il conteggio. Questo consente di eseguire query di completamento automatico in O(m) senza percorrere tutti i figli: un'estensione utile per i sistemi di completamento automatico reali.
class TrieNodeCount:
def __init__(self):
self.children = {}
self.is_end = False
self.count = 0 # words passing through this node
class TrieCount:
def __init__(self):
self.root = TrieNodeCount()
def insert(self, word):
node = self.root
for c in word:
if c not in node.children:
node.children[c] = TrieNodeCount()
node = node.children[c]
node.count += 1 # increment on each level
node.is_end = True
def count_with_prefix(self, prefix):
node = self.root
for c in prefix:
if c not in node.children: return 0
node = node.children[c]
return node.count
t = TrieCount()
for w in ['apple','app','application','apply']:
t.insert(w)
print(t.count_with_prefix('app')) # 4
print(t.count_with_prefix('appl')) # 3Confronto tra Trie e mappa hash
Una mappa hash può eseguire una ricerca esatta in tempo medio O(m), ma non può rispondere in modo efficiente alle query sui prefissi, che richiederebbero di esaminare tutte le chiavi. Un Trie risponde alle query sui prefissi in O(p), dove p è la lunghezza del prefisso, raggruppa naturalmente le parole in base ai prefissi condivisi e non richiede hashing. Si utilizzi un Trie quando sono frequenti le query sui prefissi, per il completamento automatico e il controllo ortografico. Si utilizzi una mappa hash quando servono soltanto ricerche esatte.
I Trie nei sistemi reali
Tra gli utilizzi dei Trie nel mondo reale vi sono: completamento automatico (suggerimenti di ricerca di Google), correttori ortografici (ricerca delle parole corrispondenti più vicine), routing IP (ricerca del prefisso più lungo nei router), testo predittivo T9 (disambiguazione dei caratteri) e resolver DNS (ricerca gerarchica dei nomi di dominio). In ogni caso, il compromesso tra O(m) per operazione e spazio O(ALPHABET × nodes) del Trie lo rende lo strumento adatto per ricerche rapide, consapevoli dei prefissi e su larga scala.
Verifica rapida
Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che: un TrieNode dispone di un dict children e di un booleano is_end, insert percorre i caratteri uno alla volta, creando i nodi necessari e impostando is_end alla fine e search verifica is_end, mentre starts_with controlla soltanto se esiste il percorso del prefisso. Nella prossima lezione aggiungeremo il completamento automatico basato sui prefissi e analizzeremo più approfonditamente il metodo starts_with.
Domande Frequenti
La lezione «Classe TrieNode: inserimento e ricerca» è gratuita?
Sì — il testo completo di «Classe TrieNode: inserimento e ricerca» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.
Cosa imparerò in «Classe TrieNode: inserimento e ricerca»?
Costruisca un TrieNode con un dizionario children e un flag is_end, implementi insert e exact-search e analizzi il tempo O(m) per operazione, dove m è la lunghezza della parola. Eserciti DSA Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare DSA Interview Prep?
Non è richiesta alcuna esperienza precedente. DSA Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.
Quanto tempo richiede la lezione «Classe TrieNode: inserimento e ricerca»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione DSA Interview Prep?
Sì. Ogni lezione DSA Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Classe TrieNode: inserimento e ricerca
- Ricerca per prefisso e Starts-With
- Ricerca con caratteri jolly ed espressioni regolari in un trie
- Word Search II: trie e backtracking su griglia