Klasa TrieNode: wstawianie i wyszukiwanie
Budować TrieNode ze słownikiem children i flagą is_end, implementować insert oraz exact-search i analizować czas O(m) każdej operacji, gdzie m oznacza długość słowa
Klasa TrieNode: wstawianie i wyszukiwanie to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DSA Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Czym jest trie?
Trie (drzewo prefiksowe) to drzewiasta struktura danych, w której każdy węzeł reprezentuje znak. Słowa są przechowywane jako ciągi znaków prowadzące od korzenia do liścia. Korzeń reprezentuje pusty ciąg znaków. Każda ścieżka od korzenia do węzła is_end = True tworzy zapisane słowo. Trie doskonale nadają się do zapytań opartych na prefiksach, takich jak autouzupełnianie, sprawdzanie pisowni i routing IP, i w tych zastosowaniach przewyższają mapy haszujące.
Projekt klasy TrieNode
Obiekt TrieNode ma dwa pola: children — słownik mapujący znaki na potomne obiekty TrieNode — oraz is_end — wartość logiczną wskazującą, czy dany węzeł jest końcem zapisanego słowa. Użycie słownika zamiast tablicy o stałym rozmiarze 26 uogólnia rozwiązanie na dowolny zestaw znaków i oszczędza pamięć w rzadkich strukturach trie. Każdy węzeł w trie reprezentuje dokładnie jedną pozycję znaku w słowach znajdujących się poniżej.
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)Operacja wstawiania
Aby wstawić słowo, należy przejść od korzenia, tworząc nowy obiekt TrieNode dla każdego znaku, którego nie ma jeszcze w polu children bieżącego węzła. Po przetworzeniu wszystkich znaków należy ustawić is_end = True w węźle końcowym. Wstawienie 'apple' i 'app' tworzy łańcuch a→p→p→l→e (is_end=True dla „apple”), a węzeł p na pozycji 3 jest również oznaczony jako is_end=True dla „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)Operacja wyszukiwania
Aby wyszukać dokładne słowo, należy przejść przez trie, podążając za każdym jego znakiem. Jeśli któregoś znaku brakuje w polu children bieżącego węzła, należy zwrócić False. Jeśli znaleziono wszystkie znaki, należy zwrócić node.is_end — wartość True tylko wtedy, gdy słowo kończy się dokładnie w tym miejscu, a nie jest jedynie prefiksem. To rozróżnienie między „istnieje prefiks” a „istnieje dokładne słowo” ma kluczowe znaczenie i jest często sprawdzane.
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 (wyszukiwanie prefiksu)
Metoda starts_with sprawdza, czy któreś z wstawionych słów ma podany prefiks. Wykonuje takie samo przejście jak wyszukiwanie, ale zamiast sprawdzać is_end, zwraca True od razu po pomyślnym przejściu przez wszystkie znaki prefiksu — oznacza to, że ścieżka prefiksu istnieje w 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)Złożoność czasowa i pamięciowa
Każda operacja na trie (insert, search, starts_with) zajmuje O(m) czasu, gdzie m to długość słowa — przechodzimy przez co najwyżej m węzłów. Złożoność pamięciowa wynosi O(ALPHABET_SIZE × N × M), gdzie N to liczba słów, a M to średnia długość słowa. W praktyce wspólne prefiksy znacznie zmniejszają zużycie pamięci. Słownik children oparty na mapie haszującej zajmuje mniej miejsca niż tablica o stałym rozmiarze 26 w rzadkich strukturach trie, kosztem nieco większego stałego narzutu przy każdym wyszukiwaniu.
Użycie tablicy zamiast słownika
Jeśli dozwolone są wyłącznie małe litery alfabetu angielskiego, można użyć tablicy o stałym rozmiarze children = [None] * 26 z indeksem ord(c) - ord('a'). Jest to szybsze rozwiązanie (wyszukiwanie potomka w O(1) zamiast w mapie haszującej) i zapewnia przewidywalny układ pamięci. Wersji ze słownikiem należy używać, gdy zestaw znaków jest duży lub nieznany, na przykład dla Unicode, a wersji tablicowej — w zadaniach konkursowych obejmujących wyłącznie małe litery.
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')) # FalseOperacja usuwania
Usuwanie z trie musi uwzględniać trzy przypadki: (1) słowo nie występuje — nic nie rób; (2) słowo występuje, ale jest prefiksem innego słowa — usuń tylko oznaczenie is_end; (3) słowo występuje i nie jest prefiksem — usuwaj węzły od dołu, zatrzymując się, gdy węzeł ma inne potomne węzły albo jest końcem innego słowa. Usuwanie rzadko pojawia się na rozmowach technicznych, ale warto znać je od strony koncepcyjnej.
Zliczanie słów z danym prefiksem
Każdy węzeł można rozszerzyć o pole count, zwiększane przy każdym przejściu przez węzeł podczas wstawiania. Aby policzyć słowa z danym prefiksem, należy przejść do węzła kończącego prefiks i zwrócić jego wartość count. Umożliwia to wykonywanie zapytań autouzupełniania w czasie O(m), bez przechodzenia przez wszystkie węzły potomne — jest to przydatne rozszerzenie rzeczywistych systemów autouzupełniania.
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')) # 3Porównanie trie i mapy haszującej
Mapa haszująca może wyszukiwać dokładne dopasowanie w średnim czasie O(m), ale nie potrafi wydajnie obsługiwać zapytań o prefiks (wymaga to przejrzenia wszystkich kluczy). Trie odpowiada na zapytania o prefiks w czasie O(p), gdzie p to długość prefiksu, w naturalny sposób grupuje słowa według wspólnych prefiksów i nie wymaga haszowania. Trie należy używać, gdy często wykonywane są zapytania o prefiks, autouzupełnianie lub sprawdzanie pisowni. Mapy haszującej należy używać, gdy potrzebne jest wyłącznie wyszukiwanie dokładnych dopasowań.
Trie w systemach używanych w praktyce
Przykłady użycia trie w rzeczywistych systemach obejmują: autouzupełnianie (podpowiedzi wyszukiwarki Google), moduły sprawdzania pisowni (wyszukiwanie najlepiej pasujących słów), routing IP (dopasowywanie najdłuższego prefiksu w routerach), przewidywanie tekstu T9 (rozstrzyganie niejednoznaczności znaków) oraz resolvery DNS (hierarchiczne wyszukiwanie domen). W każdym z tych przypadków kompromis między czasem O(m) operacji a zużyciem pamięci O(ALPHABET × nodes) sprawia, że trie jest właściwym narzędziem do szybkiego wyszukiwania uwzględniającego prefiksy na dużą skalę.
Szybki test
Sprawdź swoją wiedzę na temat zagadnień Data Structures & Algorithms — Coding Interview Prep z tej lekcji.
Podsumowanie lekcji
W tej lekcji dowiedział się Pan / dowiedziała się Pani, że: TrieNode ma słownik children i wartość logiczną is_end, operacja insert przechodzi przez znaki, tworząc w razie potrzeby węzły i ustawiając is_end na końcu, a także że search sprawdza is_end, podczas gdy starts_with sprawdza tylko istnienie ścieżki prefiksu. W następnej części dodamy autouzupełnianie oparte na prefiksach i dokładniej omówimy metodę starts_with.
Często zadawane pytania
Czy lekcja „Klasa TrieNode: wstawianie i wyszukiwanie” jest bezpłatna?
Tak — pełny tekst „Klasa TrieNode: wstawianie i wyszukiwanie” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DSA Interview Prep, przejdź na CoddyKit PRO. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Klasa TrieNode: wstawianie i wyszukiwanie”?
Budować TrieNode ze słownikiem children i flagą is_end, implementować insert oraz exact-search i analizować czas O(m) każdej operacji, gdzie m oznacza długość słowa Ćwiczysz DSA Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DSA Interview Prep?
Nie wymagamy żadnego doświadczenia. DSA Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Klasa TrieNode: wstawianie i wyszukiwanie”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DSA Interview Prep?
Tak. Każda lekcja DSA Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Klasa TrieNode: wstawianie i wyszukiwanie
- Wyszukiwanie prefiksów i Starts-With
- Wyszukiwanie z symbolami wieloznacznymi i wyrażeniami regularnymi w Trie
- Word Search II: Trie i przeszukiwanie z nawrotami na siatce