Bölüm 5: Trie Veri Yapısı

Algoritmalar
Önek ağacıDüğüm yapısıEklemeArama ve önek sorgusu Otomatik tamamlamaSilmeKarmaşıklık ve bellekSıkıştırılmış ve sonek trie'leri
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Yapı

Trie'nin kök, düğüm, kenar ve kelime sonu işaretinden oluşan yapısını açıklamak; bir kelime kümesinin trie'sini çizmek.

İşlemler

Ekleme, arama, önek sorgusu, otomatik tamamlama ve silme işlemlerini Java ile yazmak ve adım adım izlemek.

Analiz

İşlemlerin O(L) zamanını ve O(N · M) belleğini gerekçelendirmek; trie'yi hash tablosu ve BST ile karşılaştırmak.

Türevler

Sıkıştırılmış trie, sonek trie'si ve sonek dizisinin hangi sorunu çözdüğünü açıklamak.

Trie Kavramı

Trie (Önek Ağacı, Prefix Tree)

  • Dizgileri (string) hızlı arama yapılabilecek biçimde saklayan ağaç benzeri bir veri yapısıdır.
  • Kök düğüm boştur; kökten aşağı her adım bir karakteri temsil eder.
  • Kelimeler, kökten başlayan yollarla saklanır; ortak önekler (prefix) tek kez saklanır.
  • Kullanım alanları: otomatik tamamlama, yazım denetimi, IP yönlendirme.
kökandandtantdaddadodo
Ad, bilgi geri getirme anlamındaki İngilizce retrieval sözcüğünden gelir (E. Fredkin, 1960). Çift çember: o düğümde bir kelime biter.

Trie Kavramı

Neden Trie?

Bir sözlükte "an" ile başlayan tüm kelimeleri bulmak istiyoruz. Farklı yapılar bu soruya nasıl yanıt verir?

Dizi / listeHer kelime tek tek karşılaştırılır: n kelime için O(n · L). Kelime sayısı büyüdükçe yavaşlar.
Hash tablosuTam kelimeyi ortalama O(L)'de bulur, ama önek sorgusu yapamaz: "an" ile "and"in hash değerleri ilişkisizdir.
TrieÖnce "a", sonra "n" dalına inilir (2 adım); o düğümün altındaki her kelime "an" ile başlar.
Ana fikir: Trie'de arama süresi saklanan kelime sayısına değil, aranan kelimenin uzunluğuna (L) bağlıdır.

Trie Kavramı

Temel Bileşenler

Kök (root)Boş düğüm; hiçbir karakteri temsil etmez, her aramanın başlangıcıdır.
Düğümler (nodes)Kökten o düğüme kadarki yol bir önektir; her düğüm çocuk düğümlere bağlantılar tutar.
Kenarlar (edges)Düğümler arası bağlantılardır; her kenar bir karakter taşır.
Kelime sonu işaretikelimeSonu (end of word): bu düğümde bir kelimenin bittiğini belirtir.
kökandandtantdaddadodo

Renkli düğüm "an" önekini temsil eder; "an" bir kelime değildir (işaretsiz), ama "and" ve "ant"in önekidir.

Trie Kavramı

Örnek: Yedi Kelimelik Trie

Kelimeler
ab · aba · abc
ad
ba · bad · bag

7 kelime, toplam 18 karakter → yalnızca 9 düğüm (+ kök).

kökababaabacabcdadbabadbadgbag
"ab" hem bir kelime hem de "aba" ile "abc"nin önekidir. Yalnızca yapıya bakarak bunu anlayamayız; bu yüzden kelimeSonu işareti şarttır.

Trie Kavramı

Örnek: Değer Taşıyan Trie

AnahtarDeğerAnahtarDeğer
A15tea3
i11ted4
in5ten12
inn9to7
kökA15i11n5n9tea3d4n12o7
Trie bir sözlük (map) gibi de kullanılabilir: kelimeSonu yerine düğümde bir değer saklanır. "t" ve "te" değer taşımaz; yalnızca önektir. Büyük harf "A", 26 küçük harflik dizi yerine daha geniş bir alfabe (ör. HashMap) gerektirir.

Uygulama

Trie Düğüm Sınıfı

public class TrieDugumu {
  TrieDugumu[] cocuk;  // 'a'..'z'
  boolean kelimeSonu;  // kelime biter mi?

  public TrieDugumu() {
    kelimeSonu = false;
    cocuk = new TrieDugumu[26];
  }
}
  • Her düğüm 26 çocuk başvurusu tutar; başlangıçta hepsi null.
  • Karakter düğümde saklanmaz: hangi karakter olduğu, düğümün ebeveyninin hangi indisinde durduğundan anlaşılır.
  • kelimeSonu: kökten buraya gelen yol bir kelime mi?
kelimeSonufalseharfindisa0ab1⁄c2⁄d3de4⁄f5⁄g6⁄h7⁄i8⁄j9⁄k10⁄l11⁄m12⁄n13⁄o14⁄p15⁄q16⁄r17⁄s18⁄t19⁄u20⁄v21⁄w22⁄x23⁄y24⁄z25⁄

and, ant, dad, do eklendikten sonra kökün cocuk dizisi: 26 hücreden yalnızca 2'si dolu (⁄ = null).

Uygulama

Karakterden İndise: ch − 'a'

chUnicodech − 'a'
'a'970
'd'1003
'n'11013
'o'11114
't'11619
'z'12225
  • Java'da char bir sayıdır; ch - 'a' küçük harfi 0..25 aralığına eşler.
  • Çocuğa erişim dizi indisiyle O(1)'dir: dugum.cocuk[ch - 'a'].
  • "and" için sırasıyla cocuk[0], cocuk[13], cocuk[3] kullanılır.
Dikkat: 'A' - 'a' = −32, 'ç' - 'a' = 134 → ArrayIndexOutOfBoundsException. Büyük harf ve Türkçe karakter için girdi dönüştürülmeli ya da HashMap kullanılmalı.

Ekleme

Ekleme (Insert)

public void ekle(String kelime) {
  TrieDugumu dugum = kok;
  for (char ch : kelime.toCharArray()) {
    int i = ch - 'a';
    if (dugum.cocuk[i] == null)
      dugum.cocuk[i] = new TrieDugumu();
    dugum = dugum.cocuk[i];
  }
  dugum.kelimeSonu = true;
}
  1. Kökten başla.
  2. Kelimenin her karakteri için ilgili çocuğa bak: cocuk[ch − 'a'].
  3. Çocuk null ise yeni düğüm oluştur; varsa (ortak önek) mevcut düğümü kullan.
  4. O çocuğa ilerle.
  5. Karakterler bitince son düğümde kelimeSonu = true yap.
Zaman: O(L) (L = kelime uzunluğu); en fazla L yeni düğüm oluşur. Aynı kelimeyi iki kez eklemek yapıyı değiştirmez.

Ekleme · Adım adım

Ekleme Adım Adım: and, ant, dad, do

kök

Başlangıçta trie yalnızca kökten oluşur; kökün 26 çocuğunun hepsi null, kelimeSonu = false.

köka

"and" ekleniyor, karakter 'a': cocuk[0] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökan

"and" ekleniyor, karakter 'n': cocuk[13] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökand

"and" ekleniyor, karakter 'd': cocuk[3] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandand

Karakterler bitti: son düğümde kelimeSonu = true (çift çember). "and" eklendi. Kökten itibaren cocuk[0] → cocuk[13] → cocuk[3] yolu kuruldu.

kökandand

"ant" ekleniyor, karakter 'a': cocuk[0] dolu → mevcut "a" düğümüne ilerlenir (ortak önek).

kökandand

"ant" ekleniyor, karakter 'n': cocuk[13] dolu → mevcut "an" düğümüne ilerlenir (ortak önek).

kökandandt

"ant" ekleniyor, karakter 't': cocuk[19] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandandtant

Karakterler bitti: son düğümde kelimeSonu = true (çift çember). "ant" eklendi. Yalnızca 1 yeni düğüm gerekti: "an" öneki paylaşıldı.

kökandandtantd

"dad" ekleniyor, karakter 'd': cocuk[3] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandandtantda

"dad" ekleniyor, karakter 'a': cocuk[0] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandandtantdad

"dad" ekleniyor, karakter 'd': cocuk[3] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandandtantdaddad

Karakterler bitti: son düğümde kelimeSonu = true (çift çember). "dad" eklendi.

kökandandtantdaddad

"do" ekleniyor, karakter 'd': cocuk[3] dolu → mevcut "d" düğümüne ilerlenir (ortak önek).

kökandandtantdaddado

"do" ekleniyor, karakter 'o': cocuk[14] null → yeni düğüm oluşturulur ve ona ilerlenir.

kökandandtantdaddadodo

Karakterler bitti: son düğümde kelimeSonu = true (çift çember). "do" eklendi. Son durum: 4 kelime, 11 karakter, kök hariç 8 düğüm.

Ekleme · Kodu adım adım çalıştır

ekle("ant"): Kodu Adım Adım İzle

kökandand

Trie'de yalnızca "and" var. dugum kökten başlar.

kökandand

Karakter 'a' → i = 'a' − 'a' = 0.

kökandand

dugum.cocuk[0] null değil → yeni düğüm gerekmez.

kökandand

dugum artık "a" düğümü.

kökandand

Karakter 'n' → i = 'n' − 'a' = 13.

kökandand

dugum.cocuk[13] null değil → yeni düğüm gerekmez.

kökandand

dugum artık "an" düğümü.

kökandand

Karakter 't' → i = 't' − 'a' = 19.

kökandand

dugum.cocuk[19] == null → koşul doğru.

kökandandt

Yeni bir TrieDugumu oluşturulup cocuk[19]'e bağlanır (kelimeSonu = false).

kökandandt

dugum artık "ant" düğümü.

kökandandtant

Döngü bitti: dugum.kelimeSonu = true. "ant" eklendi; yalnızca 1 düğüm oluşturuldu.

public void ekle(String kelime) {
  TrieDugumu dugum = kok;
  for (char ch : kelime.toCharArray()) {
    int i = ch - 'a';
    if (dugum.cocuk[i] == null)
      dugum.cocuk[i] = new TrieDugumu();
    dugum = dugum.cocuk[i];
  }
  dugum.kelimeSonu = true;
}

Arama

Arama (Search)

public boolean ara(String kelime) {
  TrieDugumu dugum = kok;
  for (char ch : kelime.toCharArray()) {
    int i = ch - 'a';
    if (dugum.cocuk[i] == null)
      return false;
    dugum = dugum.cocuk[i];
  }
  return dugum.kelimeSonu;
}
  • Kökten başlanır.
  • Her karakter için ilgili dala (çocuğa) gidilir.
    • Dal yoksa (null) kelime trie'de yoktur.
  • Kelimenin sonuna gelince kelimeSonu denetlenir:
    • true → kelime bulundu;
    • false → kelime yok; yalnızca var olan bir kelimenin önekidir.
Zaman: O(L). Trie'de bir milyon kelime de olsa "dad" en fazla 3 adımda bulunur.

Arama · Kodu adım adım çalıştır

ara("dad"): Kodu Adım Adım İzle

kökandandtantdaddadodo

Trie: and, ant, dad, do. dugum kökten başlar.

kökandandtantdaddadodo

Karakter 'd' → i = 3.

kökandandtantdaddadodo

cocuk[3] null değil → dal var, devam.

kökandandtantdaddadodo

dugum = "d".

kökandandtantdaddadodo

Karakter 'a' → i = 0.

kökandandtantdaddadodo

cocuk[0] null değil → dal var, devam.

kökandandtantdaddadodo

dugum = "da".

kökandandtantdaddadodo

Karakter 'd' → i = 3.

kökandandtantdaddadodo

cocuk[3] null değil → dal var, devam.

kökandandtantdaddadodo

dugum = "dad".

kökandandtantdaddadodo

Kelime bitti; dugum.kelimeSonu = true → true döner (kelime bulundu).

public boolean ara(String kelime) {
  TrieDugumu dugum = kok;
  for (char ch : kelime.toCharArray()) {
    int i = ch - 'a';
    if (dugum.cocuk[i] == null)
      return false;
    dugum = dugum.cocuk[i];
  }
  return dugum.kelimeSonu;
}

Arama

Aramanın Üç Olası Sonucu

ara("dad") → trueTüm karakterler bulundu ve son düğüm işaretli.
kökandtdado
ara("da") → falseYol var, ama "da" düğümünde kelimeSonu = false: "da" yalnızca önek.
kökandtdado
ara("dog") → false"do" düğümünde cocuk[6] ('g') null: dal yok, erken çıkış.
kökandtdadog

Kesikli kırmızı çember: aranan ama trie'de bulunmayan düğüm.

Arama

Önek Sorgusu: onekVarMi (startsWith)

public boolean onekVarMi(String onek) {
  TrieDugumu dugum = kok;
  for (char ch : onek.toCharArray()) {
    int i = ch - 'a';
    if (dugum.cocuk[i] == null)
      return false;
    dugum = dugum.cocuk[i];
  }
  return true;  // ara: dugum.kelimeSonu
}
SorguaraonekVarMi
"dad"truetrue
"da"falsetrue
"an"falsetrue
"dog"falsefalse
Tek fark son satırdadır: önek sorgusu kelime sonunu denetlemez; yolun var olması yeterlidir. Trie: and, ant, dad, do. Zaman: O(P), P = önek uzunluğu.

Arama · Kodu adım adım çalıştır

Otomatik Tamamlama: tamamla("te")

kökaaiininninnteateadtedntenoto

Önce dugumBul("te") kökten t → e inerek "te" düğümünü bulur (önek sorgusu gibi).

kökaaiininninnteateadtedntenoto

"te" düğümünden başlayarak alt ağaç derinlik öncelikli gezilir.

kökaaiininninnteateadtedntenoto

"te" bir kelime değil (kelimeSonu = false) → listeye eklenmez.

kökaaiininninnteateadtedntenoto

i = 0: cocuk[0] ('a') dolu → yol = "tea".

kökaaiininninnteateadtedntenoto

Özyinelemeli çağrı: topla("tea").

kökaaiininninnteateadtedntenoto

"tea" işaretli → sonuc'a eklenir. Çocuğu yok; döngü 26 indisi boş geçer.

kökaaiininninnteateadtedntenoto

Çağrı döndü; son harf silinir (yol = "te") → geri izleme (backtracking).

kökaaiininninnteateadtedntenoto

i = 3: cocuk[3] ('d') dolu → yol = "ted".

kökaaiininninnteateadtedntenoto

Özyinelemeli çağrı: topla("ted").

kökaaiininninnteateadtedntenoto

"ted" işaretli → sonuc'a eklenir. Çocuğu yok; döngü 26 indisi boş geçer.

kökaaiininninnteateadtedntenoto

Çağrı döndü; son harf silinir (yol = "te") → geri izleme (backtracking).

kökaaiininninnteateadtedntenoto

i = 13: cocuk[13] ('n') dolu → yol = "ten".

kökaaiininninnteateadtedntenoto

Özyinelemeli çağrı: topla("ten").

kökaaiininninnteateadtedntenoto

"ten" işaretli → sonuc'a eklenir. Çocuğu yok; döngü 26 indisi boş geçer.

kökaaiininninnteateadtedntenoto

Çağrı döndü; son harf silinir (yol = "te") → geri izleme (backtracking).

kökaaiininninnteateadtedntenoto

Döngü bitti; tamamla("te") → [tea, ted, ten]. İndis sırası (a < d < n) sayesinde sonuçlar alfabetik sıradadır.

List<String> tamamla(String onek) {
  List<String> sonuc = new ArrayList<>();
  TrieDugumu d = dugumBul(onek);
  if (d != null)
    topla(d, new StringBuilder(onek), sonuc);
  return sonuc;
}
void topla(TrieDugumu d, StringBuilder yol,
           List<String> sonuc) {
  if (d.kelimeSonu) sonuc.add(yol.toString());
  for (int i = 0; i < 26; i++)
    if (d.cocuk[i] != null) {
      yol.append((char) ('a' + i));
      topla(d.cocuk[i], yol, sonuc);
      yol.setLength(yol.length() - 1);
    }
}

Arama

Deneyin: Trie Üzerinde İşlemler

Yalnızca a–z küçük harfler (en fazla 10 harf, 14 kelime). Çift çember: kelime sonu · turuncu: son ziyaret edilen düğüm · kesikli kırmızı: bulunamayan dal. Enter = ekle.

Silme

Silme (Delete): Üç Durum

1. Kelime başka bir kelimenin önekiSon düğümün çocuğu vardır → yalnızca kelimeSonu = false yapılır, düğüm silinmez.
2. Kelimenin kendine ait bir kuyruğu varSondan başa doğru, çocuğu kalmayan ve işaretsiz düğümler silinir; paylaşılan önekte durulur.
3. Kelime trie'de yokDal bulunamaz ya da son düğüm işaretsizdir → hiçbir şey değişmez.
Soru: Trie'de "apple" ve "app" varsa, "app" silindiğinde yapı nasıl değişir?
Cevabı göster

Yalnızca son 'p' düğümünün kelimeSonu işareti kaldırılır. Düğüm silinemez, çünkü "apple" onun altından devam eder (durum 1).

Silme · Adım adım

Silme Adım Adım: app, apple, apt

kökappappleappletapt

Başlangıç: app, apple, apt (kök hariç 6 düğüm). Aşağıdaki iki senaryo bu trie'den başlar.

kökappappleappletapt

sil("app"): a → p → p yolu izlenir; "app" düğümü işaretli, yani kelime var.

kökappleappletapt

kelimeSonu = false. Düğümün çocuğu ('l') olduğu için silinmez → durum 1. "apple" hâlâ bulunur.

kökappappleappletapt

sil("apple") (yeniden ilk trie'den): yol a-p-p-l-e izlenir; "apple" bulundu.

kökappappletapt

'e' düğümünün işareti kalkar; çocuğu yok → ebeveyn onu siler (cocuk[4] = null).

kökappappltapt

'l' düğümü artık çocuksuz ve işaretsiz → o da silinir.

kökappapptapt

"app" düğümü işaretli (başka bir kelime) → silme burada durur. Sonuç: app, apt; kök hariç 4 düğüm (durum 2).

Silme

Silme: Özyinelemeli Kod

// true: ebeveyn d'yi silebilir
boolean sil(TrieDugumu d, String k, int j) {
  if (j == k.length()) {        // kelime bitti
    if (!d.kelimeSonu) return false;
    d.kelimeSonu = false;
    return bosMu(d);            // çocuksuz mu?
  }
  int i = k.charAt(j) - 'a';
  TrieDugumu c = d.cocuk[i];
  if (c == null) return false;  // kelime yok
  if (sil(c, k, j + 1)) {
    d.cocuk[i] = null;          // çocuğu sil
    return !d.kelimeSonu && bosMu(d);
  }
  return false;
}
  • Çağrı: sil(kok, kelime, 0); kökün dönüş değeri yok sayılır.
  • Önce kelimenin sonuna inilir, silme kararları dönüşte (aşağıdan yukarı) verilir.
  • Düğüm, ancak çocuğu yoksa ve işaretsizse silinir.
  • bosMu(d): 26 çocuğun hepsi null mı? (O(M)).
  • Zaman: O(L · M) dizi ile; çocuk sayacı tutulursa O(L).

Analiz

Karmaşıklık

İşlemZamanAçıklama
ekle(kelime)O(L)Her karakter için bir adım; en fazla L yeni düğüm
ara(kelime)O(L)Dal yoksa daha erken biter
onekVarMi(onek)O(P)P: önek uzunluğu
tamamla(onek)O(P + S · M)S: önek düğümünün alt ağacındaki düğüm sayısı
sil(kelime)O(L · M)bosMu 26 hücreyi tarar; çocuk sayacıyla O(L)
Bellek: O(N · M)N: düğüm sayısı, M: alfabe boyutu (26). N ≤ (tüm kelimelerin toplam uzunluğu) + 1.
n'den bağımsızSüreler saklanan kelime sayısına bağlı değildir; BST'deki log n çarpanı yoktur.

Analiz

Bellek: Sayısal Örnek

Trie: and, ant, dad, do

Toplam karakter3 + 3 + 3 + 2 = 11
Düğüm sayısı N1 (kök) + 8 = 9
Çocuk başvurusu9 × 26 = 234
Dolu başvuru8 (kenar sayısı)
Boş (null) başvuru226 (≈ %97)
kökandandtantdaddadodo
Ortak önekler 11 karakteri 8 düğüme indirir, ama 26 elemanlı diziler belleğin büyük kısmını null ile doldurur. Alfabe büyüdükçe (Unicode: 65 536+) sorun büyür.

Analiz

Avantajlar ve Dezavantajlar

Avantajlar

  • Hızlı önek (prefix) tabanlı arama: otomatik tamamlama.
  • Kelime arama O(L) zaman (L: kelime uzunluğu).
  • Çakışan önekler tek kez saklanır: bellek paylaşımı.
  • Gezinme kelimeleri sözlük sırasında verir; hash çakışması yoktur.

Dezavantajlar

  • Yüksek bellek tüketimi, özellikle büyük alfabelerde.
  • Bellek karmaşıklığı O(N · M) (N: düğüm sayısı, M: alfabe boyutu).
  • Ortak öneki az olan kümelerde (ör. rastgele anahtarlar) kazanç yoktur.
  • Çok sayıda küçük nesne: önbellek (cache) dostu değildir.

Analiz

Çocukları Saklamanın Yolları

GösterimÇocuğa erişimDüğüm başına bellekNot
TrieDugumu[26] diziO(1)M başvuruBasit, hızlı; seyrek düğümlerde israf
HashMap<Character, TrieDugumu>O(1) ort.çocuk sayısı kadarHer alfabe (Türkçe, Unicode) çalışır; sıra korunmaz
TreeMap / sıralı listeO(log M)çocuk sayısı kadarAlfabetik gezinme korunur
Üçlü arama trie'si (TST)O(log M) ort.3 başvuruHer düğümde küçük / eşit / büyük bağlantısı
Trie'nin mantığı değişmez; yalnızca dugum.cocuk[i] yerine dugum.cocuk.get(ch) gibi bir çağrı gelir. Seçim: alfabe boyutu, bellek ve sıralı çıktı ihtiyacına göre yapılır.

Analiz

Trie mi, Hash Tablosu mu, BST mi?

İşlem (n kelime, uzunluk L)TrieHash tablosuDengeli BST
Tam kelime aramaO(L)O(L) ortalamaO(L · log n)
Önek sorgusuO(P)desteklenmezO(P · log n)
Önekle başlayan k kelimeO(P + S · M)tüm tabloyu taraO(P · log n + k · L)
Sıralı listelemeDFS, O(N · M)sıralama gerekirkök ortada, O(n)
En kötü durumO(L)O(n · L) (çakışma)O(L · log n)
Bellekyüksekdüşükdüşük

Hash tablosu da kelimenin hash değerini hesaplamak için L karakterin tümünü okur; bu yüzden tam aramada ikisi aynı mertebededir. Trie'nin farkı önek ve sıra sorgularındadır. S: önek düğümünün alt ağacındaki düğüm sayısı.

Uygulamalar ve Türevler

Kullanım Alanları

Otomatik tamamlamaArama kutusu, IDE: yazılan önek düğümüne inilir, alt ağaçtaki kelimeler (sık kullanılanlar önce) önerilir.
Yazım denetimiSözlük trie'de tutulur; kelime yoksa trie üzerinde küçük düzenlemelerle (ekle / sil / değiştir) yakın kelimeler aranır.
IP yönlendirmeİkili trie (alfabe {0, 1}) ile en uzun önek eşleşmesi: 0110… adresi, 0* / 01* / 011* yollarından en uzununa, 011*'e gider.
Kelime oyunları ve T9Boggle / kelime bulmaca: yol bir önek değilse arama hemen kesilir (budama). Tuş dizisinden kelime önerme.
En uzun önek eşleşmesi: rotalar 0* → A, 01* → B, 011* → C. Adres 0110… için kökten 0 → 1 → 1 inilir; '0' dalı yoktur. Yol boyunca görülen son rota C'dir → paket C'ye gider.
kök0A1B1C0

Uygulamalar ve Türevler

Sıkıştırılmış Trie (Radix / Patricia)

Standart trie: 8 düğüm
kökandandtantdaddadodo
Sıkıştırılmış trie: 6 düğüm
anddtadokökandantdaddo
  • Tek çocuklu ve işaretsiz düğüm zincirleri tek bir kenarda birleştirilir; kenar artık bir alt dizgi taşır ("an", "ad").
  • Her iç düğümün en az 2 çocuğu (ya da kelime sonu işareti) olduğundan düğüm sayısı O(n) olur (n: kelime sayısı).
  • Patricia (Morrison, 1968) bu fikrin ikili alfabe sürümüdür; IP yönlendirme tablolarında kullanılır.

Uygulamalar ve Türevler

Sonek Trie'si (Suffix Trie)

T = "abab" metninin tüm sonekleri bir trie'ye eklenir:

abab · bab · ab · b

  • P, T'nin bir alt dizgisi ⇔ P, bir sonekin öneki ⇔ P kökten başlayan bir yol.
  • Sorgu: onekVarMi(P) → O(m) (m = |P|), metnin uzunluğundan bağımsız. Örn. "ba" var, "bb" yok.
  • Bedel: n uzunluklu metin için O(n²) düğüm olabilir.
  • Sonek ağacı (suffix tree) = sıkıştırılmış sonek trie'si: O(n) düğüm, Ukkonen algoritmasıyla O(n) zamanda kurulur.
kökabababababbbabbab

Uygulamalar ve Türevler

Sonek Dizisi (Suffix Array)

sıraSA[sıra]sonek
06a
12abama
20alabama
34ama
43bama
51labama
65ma
  • Sonek trie'sinin bellek dostu alternatifi: soneklerin başlangıç indisleri, sözlük sırasına göre dizilir.
  • "alabama" → SA = [6, 2, 0, 4, 3, 1, 5].
  • Kurulum: tüm sonekleri oluşturup Arrays.sort ile sırala → O(n² log n) en kötü; özel algoritmalarla O(n log n) ya da O(n).
  • Arama: SA üzerinde ikili arama → O(m log n). Örn. "ama" için 3. sıradaki sonek (indis 4) bulunur.
  • Yalnızca n tamsayı: trie'deki O(n²) düğüm yerine O(n) bellek.

Özet

Özet

KonuHatırlanacaklar
Yapıboş kök; her kenar bir karakter; kelime = kökten yol; ortak önekler paylaşılır
DüğümTrieDugumu[26] cocuk + boolean kelimeSonu; indis = ch − 'a'
Eklemeyoksa düğüm oluştur, varsa ilerle; sonda işaretle — O(L)
Aramadal yoksa false; sonda kelimeSonu döner — O(L)
Önekaynı yol, işaret denetimi yok — O(P); tamamla = önek düğümünden DFS
Silmeişareti kaldır; çocuksuz ve işaretsiz düğümleri aşağıdan yukarı sil
BellekO(N · M); seyrek düğümlerde HashMap / TST / sıkıştırılmış trie
Türevlerradix/Patricia trie, sonek trie'si → sonek ağacı, sonek dizisi

Özet

Etkileşimli Simülatörler

Trie ve Sonek YapılarıVirgülle ayrılmış kelimelerden trie kurun ve önek sorgusunu izleyin; bir metnin sonek dizisini ve sonek trie'sini karşılaştırın.
Önek Fonksiyonu ve Z AlgoritmasıAynı dizgi için KMP önek (π) dizisini ve Z dizisini doğrusal zamanda kurup yan yana karşılaştırın.

Özet

Sık Yapılan Hatalar

kelimeSonu'nu unutmakAramada yalnızca yolun varlığına bakmak her öneki kelime sayar: "da" bulunmuş gibi görünür.
ara ile onekVarMi'yi karıştırmakÖnek sorgusu son düğümün işaretine bakmaz; tam kelime araması bakmak zorundadır.
Alfabe dışı karakter'A' − 'a' = −32, 'ş' − 'a' = 254: dizi sınırı aşılır. Girdiyi küçük harfe çevirin ya da HashMap kullanın.
Silmede ortak düğümü silmekYolu baştan sona silmek, öneki paylaşan diğer kelimeleri de yok eder. Yalnızca çocuksuz ve işaretsiz düğümler silinir.
Karmaşıklığı n ile ifade etmekTrie işlemleri O(log n) ya da O(n) değil, O(L)'dir: kelime sayısından bağımsızdır.
Belleği hafife almakHer düğüm 26 başvuru taşır; milyonlarca düğümde yüzlerce MB. Seyrek trie'de HashMap ya da sıkıştırma gerekir.

Özet

Örnek Problemler

Kelimeler sırayla eklenir: car, cart, care, cat, dog (26 elemanlı dizili trie).

  1. Trie'yi çizin. Kök dahil kaç düğüm vardır?
  2. ara("ca") ve onekVarMi("ca") ne döndürür?
  3. tamamla("car") sonucunu sırasıyla yazın.
  4. sil("car") ve (ilk trie'den) sil("cart") kaç düğüm siler?
  5. Toplam kaç çocuk başvurusu null'dır?
  6. "banana" metninin sonek dizisini bulun.
Cevapları göster
  1. c-a-r(•)-t(•), car-e(•), ca-t(•); d-o-g(•) → kök hariç 9, kök dahil 10 düğüm (17 karakter).
  2. ara("ca") = false ("ca" işaretsiz); onekVarMi("ca") = true.
  3. [car, care, cart] — DFS önce düğümün kendisini, sonra çocukları indis sırasıyla ('e' < 't') verir.
  4. sil("car"): 0 düğüm (yalnızca işaret kalkar); sil("cart"): 1 düğüm ('t'), "car" işaretli olduğu için durur.
  5. 10 × 26 = 260 başvuru, 9'u dolu (kenar sayısı) → 251 null.
  6. Sonekler sıralanır: a, ana, anana, banana, na, nana → SA = [5, 3, 1, 0, 4, 2].

Özet

Alıştırmalar

  1. int kelimeSayisi(TrieDugumu d): trie'deki kelime sayısını bulun.
  2. int onekSayisi(String onek): bu önekle başlayan kelime sayısını O(P)'de döndürün.
  3. String enUzunOrtakOnek(): trie'deki tüm kelimelerin en uzun ortak önekini bulun.
  4. Çocukları HashMap<Character, TrieDugumu> ile saklayan, Türkçe karakterleri destekleyen trie yazın.
  5. A, to, tea, ted, ten, i, in, inn anahtarlarının trie'sinde kök dahil kaç düğüm vardır?
  6. Sonek trie'si ile "abab" içinde "bab" ve "bb" alt dizgilerini arayın; kaç karşılaştırma yapılır?
Cevaplar / ipuçları
  1. DFS: (d.kelimeSonu ? 1 : 0) + tüm dolu çocukların kelimeSayisi toplamı. O(N · M).
  2. Düğüme int gecis sayacı ekleyin; ekle her geçtiği düğümde artırsın. Önek düğümünün sayacı = cevap.
  3. Kökten inin: düğümün tek çocuğu var ve kelimeSonu false iken ilerleyin; geçilen karakterler cevaptır.
  4. cocuk.get(ch) / cocuk.computeIfAbsent(ch, x -> new TrieDugumu()); indis hesabı kalkar.
  5. A, t, to, te, tea, ted, ten, i, in, inn = 10 düğüm + kök = 11.
  6. "bab": b → a → b yolu var → bulundu (3 adım). "bb": b'den sonra 'b' dalı yok → yok (2 adım).

Özet

Kaynaklar

  • Sedgewick ve Wayne, Algorithms, 4. baskı, 2011 (Bölüm 5.2: Tries).
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
  • E. Fredkin, "Trie Memory", Communications of the ACM 3(9), 1960.
  • D. R. Morrison, "PATRICIA — Practical Algorithm To Retrieve Information Coded in Alphanumeric", Journal of the ACM 15(4), 1968.
  • U. Manber ve G. Myers, "Suffix Arrays: A New Method for On-Line String Searches", SIAM Journal on Computing 22(5), 1993.
  • D. Gusfield, Algorithms on Strings, Trees, and Sequences, Cambridge University Press, 1997.

Son

Bölüm 5: Trie Veri Yapısı
Önceki bölüm: Bölüm 5: Dizgi Algoritmaları (2) · Sonraki bölüm: Bölüm 6: Dinamik Programlama
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.