Trie — Önek Ağacı

Trie önek ağacı oluşturma, kelime ekleme-arama-silme O(m), önek arama, otomatik tamamlama DFS — adım adım SVG görseller, karşılaştırma tabloları ve C kodu.

🗂️ Veri Yapıları 📅 21 February 2026 👁️ 2 görüntülenme

Trie Nedir?

Trie (okunuşu: "tray"), her kenarın bir karakter temsil ettiği özel bir ağaç yapısıdır. Kökten herhangi bir düğüme giden yol bir önek (prefix) oluşturur. Kelime sonunu gösteren düğümler işaretlenir. Adı "retrieval" kelimesinden gelir.

BST / Hash Table ile farkı

BST: Kelime arama O(m × log n) — her karşılaştırma O(m).
Hash: Kelime arama O(m) ortalama — ama önek arama yapamaz.
Trie: Kelime arama O(m) garanti — ve önek arama da O(m)!

Nerelerde kullanılır?

Otomatik tamamlama (Google arama)
Yazım denetimi (spell checker)
IP yönlendirme (longest prefix match)
T9 klavye / kelime tahmin
Temel fikir: Ortak önekleri paylaş. "araba", "ara", "arı" kelimeleri ilk 2 karakteri (a-r) paylaşır → Trie'de bu 2 karakter tek bir yolda saklanır. n kelime yerine ortak önekler sıkıştırılır → hem bellek hem hız kazancı.

Referans Trie — 5 Kelime

Kelimeler: "araba""ara""arı""bal""bak"

5 Kelimelik Trie — Paylaşılan Önekler
kökababraraaılka★ araı★ arıl★ balk★ bakbbaa★ araba
 = isEndOfWord=true. "ara" hem kelime hem de "araba"nın öneki — ikisi de aynı yolda.

Trie Düğüm Yapısı

trie.c — yapı ve yardımcılar
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define ALFABE_BOYUTU 26  // a-z (küçük İngilizce harf)

typedef struct TrieNode {
    struct TrieNode* children[ALFABE_BOYUTU];
    bool             isEndOfWord;  // ★ kelime sonu mu?
} TrieNode;

TrieNode* yeniDugum() {
    TrieNode* node = (TrieNode*)calloc(1, sizeof(TrieNode));
    // calloc ile tüm children = NULL, isEndOfWord = false
    return node;
}

/* Karakter → indeks dönüşümü */
int charIndex(char c) {
    return c - 'a';
}
Alfabe boyutu seçimi: children[26] sadece küçük İngilizce harfler (a-z) içindir. Türkçe karakterler (ç, ğ, ı, ö, ş, ü) için 32'ye çıkarılabilir veya hash map tabanlı children kullanılabilir. Unicode desteği için genellikle hash map tercih edilir. Bu yazıda kavramsal basitlik için 26 harf kullanıyoruz.

Kelime Ekleme (Insert)

Ekleme, kelimenin her karakteri için kökten başlayarak ağaçta ilerler. Karakter için çocuk yoksa yeni düğüm oluşturur, varsa mevcut düğüme geçer. Son karakterde isEndOfWord = true işaretler.

trieEkle() — O(m), m = kelime uzunluğu
void trieEkle(TrieNode* root, const char* kelime) {
    TrieNode* curr = root;

    for (int i = 0; kelime[i]; i++) {
        int idx = charIndex(kelime[i]);

        if (!curr->children[idx])
            curr->children[idx] = yeniDugum();  // yeni düğüm

        curr = curr->children[idx];              // ilerle
    }
    curr->isEndOfWord = true;                   // ★ kelime sonu
}

Adım Adım — 3 Kelime Ekleme

① insert("araba") → ② insert("ara") → ③ insert("arı")
① "araba" — 5 yeni düğüm
kökaarraabbaa★ araba
② "ara" — 0 yeni düğüm!
kökaarraa★ ara (yeni)ba★ araba
③ "arı" — 1 yeni düğüm (ı)
kökaarraa★ araıı★ arı (yeni)ba★ araba
"ara" eklenirken 0 yeni düğüm → sadece isEndOfWord=true. "arı" eklenirken "a-r" yolu paylaşılır, sadece "ı" düğümü yeni.

Kelime Arama (Search)

Arama, kelimenin her karakteri için kökten ilerler. Son karaktere ulaşılırsa ve isEndOfWord == true ise kelime bulunmuştur. Düğüm varsa ama isEndOfWord false ise → kelime yok, sadece önek var.

Arama: "ara" ✓ vs "ar" ✗
search("ara") → BULUNDU ✓
kök →a→ →r→ →a→ ★ isEnd=true
search("ar") → BULUNAMADI ✗
kök →a→ →r→ isEnd=false
"ar" düğümü var ama kelime olarak işaretlenmemiş → arama false döner.
trieAra() — kelime tam eşleşmesi O(m)
bool trieAra(TrieNode* root, const char* kelime) {
    TrieNode* curr = root;

    for (int i = 0; kelime[i]; i++) {
        int idx = charIndex(kelime[i]);
        if (!curr->children[idx])
            return false;   // karakter yok → kelime yok
        curr = curr->children[idx];
    }
    return curr->isEndOfWord;   // düğüm var ama kelime mi?
}

Önek Arama (Starts With)

Önek arama, kelime aramanın neredeyse aynısıdır — tek fark: sonda isEndOfWord kontrolü yapılmaz. Önek yolu varsa yeterlidir.

onekVar() — önek sorgulama O(m)
bool onekVar(TrieNode* root, const char* onek) {
    TrieNode* curr = root;

    for (int i = 0; onek[i]; i++) {
        int idx = charIndex(onek[i]);
        if (!curr->children[idx])
            return false;
        curr = curr->children[idx];
    }
    return true;   // isEndOfWord kontrol etmiyoruz!
}
Arama vs Önek: trieAra("ar") → false (kelime yok). onekVar("ar") → true (önek var — "ara", "araba", "arı" bu önekle başlar). Tek satır fark: return curr->isEndOfWord yerine return true.

Kelime Silme (Delete)

Silme, Trie'nin en karmaşık operasyonudur. Bir kelimeyi silerken başka kelimelerin yolunu bozmamaya dikkat etmek gerekir. Üç farklı durum ortaya çıkar:

Durum 1: Başka kelimenin öneki

"ara" sil → "araba" hâlâ var. Sadece isEndOfWord = false yap. Düğüm silinmez.

Durum 2: Benzersiz son ek

"arı" sil → "ı" düğümü başka kelimede yok → düğümü sil. "a-r" yolu "araba" için kalır.

Durum 3: Tamamen benzersiz

"bak" sil → "b-a-k" yolu başka kelimede yoksa (ama "bal" "b-a"yı paylaşır) → sadece "k" düğümünü sil.

Yardımcı: Çocuğu Var mı?

cocuguVar() — düğüm yaprak mı?
bool cocuguVar(TrieNode* node) {
    for (int i = 0; i < ALFABE_BOYUTU; i++)
        if (node->children[i])
            return true;
    return false;
}

Silme Kodu — Özyinelemeli

trieSil() — rekürsif, O(m)
/* Dönüş: bu düğüm silinmeli mi? */
bool trieSilHelper(TrieNode* node, const char* kelime, int derinlik) {
    if (!node) return false;

    // Kelimenin sonuna ulaştık
    if (derinlik == strlen(kelime)) {
        if (!node->isEndOfWord)
            return false;   // kelime yok

        node->isEndOfWord = false;  // ★ işareti kaldır

        // Çocuğu yoksa silinebilir
        return !cocuguVar(node);
    }

    int idx = charIndex(kelime[derinlik]);
    bool silmeli = trieSilHelper(
        node->children[idx], kelime, derinlik + 1
    );

    if (silmeli) {
        free(node->children[idx]);
        node->children[idx] = NULL;

        // Bu düğüm de silinebilir mi?
        return !node->isEndOfWord && !cocuguVar(node);
    }

    return false;
}

void trieSil(TrieNode* root, const char* kelime) {
    trieSilHelper(root, kelime, 0);
}

Adım Adım — Silme Örnekleri

delete("ara") → Durum 1 | delete("arı") → Durum 2
delete("ara") — sadece isEnd=false
kök→a→r→a ★ →b→a ★
Sonuç: a düğümü kalır (araba için)
sadece isEndOfWord=false yapıldı
delete("arı") — düğüm silinir
kök→a→r→ı ★
"ı" çocuğu yok, başka kelime yok
düğüm free() ile silinir
Rekürsif geri dönüşte her düğüm "silinmeli mi?" sorusunu yanıtlar: çocuğu yoksa VE kelime sonu değilse → sil.

Otomatik Tamamlama (Autocomplete)

Trie'nin en güçlü özelliği: bir önek verildiğinde o önekle başlayan tüm kelimeleri hızlıca bulmak. Algoritma iki adımdan oluşur:

Autocomplete Algoritması:
Adım 1: Önekin son düğümüne git (önek arama ile aynı) → O(p), p = önek uzunluğu.
Adım 2: Bu düğümden DFS ile tüm alt ağacı tara, isEndOfWord=true olan yolları topla.
Toplam: O(p + k) — k = sonuç sayısı × ortalama kelime uzunluğu.

Adım Adım — Önek "ar"

autocomplete("ar") → 3 sonuç
kökaarrDFS buradanaıa★ araı★ arıba★ araba
Sonuç: ara araba arı
otomatikTamamla() — önek + DFS
/* DFS ile alt ağaçtaki tüm kelimeleri topla */
void dfsTara(TrieNode* node, char* buffer, int derinlik) {
    if (!node) return;

    if (node->isEndOfWord) {
        buffer[derinlik] = '\0';
        printf("  → %s\n", buffer);
    }

    for (int i = 0; i < ALFABE_BOYUTU; i++) {
        if (node->children[i]) {
            buffer[derinlik] = 'a' + i;
            dfsTara(node->children[i], buffer, derinlik + 1);
        }
    }
}

void otomatikTamamla(TrieNode* root, const char* onek) {
    TrieNode* curr = root;

    // Adım 1: Önekin son düğümüne git
    for (int i = 0; onek[i]; i++) {
        int idx = charIndex(onek[i]);
        if (!curr->children[idx]) {
            printf("'%s' öneki bulunamadı.\n", onek);
            return;
        }
        curr = curr->children[idx];
    }

    // Adım 2: DFS ile tüm sonuçları topla
    char buffer[256];
    strcpy(buffer, onek);
    printf("'%s' ile başlayanlar:\n", onek);
    dfsTara(curr, buffer, strlen(onek));
}

Tam Demo Program

Yardımcı: Kelime Sayısı

kelimeSayisi() — Trie'deki toplam kelime
int kelimeSayisi(TrieNode* node) {
    if (!node) return 0;
    int sayac = node->isEndOfWord ? 1 : 0;
    for (int i = 0; i < ALFABE_BOYUTU; i++)
        sayac += kelimeSayisi(node->children[i]);
    return sayac;
}

main() — Tüm Operasyonlar

main() — ekle, ara, önek, otomatik tamamla, sil
int main() {
    TrieNode* root = yeniDugum();

    // ── Kelime Ekleme ──
    printf("=== Trie Demo ===\n\n");
    const char* kelimeler[] = {
        "araba", "ara", "arı", "bal",
        "bak", "balık", "can", "canan"
    };
    int n = 8;

    printf("--- Ekleme ---\n");
    for (int i = 0; i < n; i++) {
        trieEkle(root, kelimeler[i]);
        printf("  insert(\"%s\")\n", kelimeler[i]);
    }
    printf("Toplam: %d kelime\n", kelimeSayisi(root));

    // ── Kelime Arama ──
    printf("\n--- Arama ---\n");
    const char* aramalar[] = {"ara","araba","ar","bal","bala","can"};
    for (int i = 0; i < 6; i++)
        printf("  search(\"%s\") → %s\n", aramalar[i],
               trieAra(root, aramalar[i]) ? "Bulundu ✓" : "Yok ✗");

    // ── Önek Sorgulama ──
    printf("\n--- Önek Arama ---\n");
    const char* onekler[] = {"ar", "ba", "ca", "de"};
    for (int i = 0; i < 4; i++)
        printf("  startsWith(\"%s\") → %s\n", onekler[i],
               onekVar(root, onekler[i]) ? "Var ✓" : "Yok ✗");

    // ── Otomatik Tamamlama ──
    printf("\n--- Otomatik Tamamlama ---\n");
    otomatikTamamla(root, "ar");
    otomatikTamamla(root, "ba");
    otomatikTamamla(root, "c");

    // ── Silme ──
    printf("\n--- Silme ---\n");
    trieSil(root, "ara");
    printf("  delete(\"ara\")\n");
    printf("  search(\"ara\")   → %s\n", trieAra(root,"ara")?"Var":"Yok ✗");
    printf("  search(\"araba\") → %s\n", trieAra(root,"araba")?"Var ✓":"Yok");

    trieSil(root, "arı");
    printf("  delete(\"arı\")\n");

    printf("\n  Silme sonrası autocomplete(\"ar\"):\n");
    otomatikTamamla(root, "ar");
    printf("Kalan: %d kelime\n", kelimeSayisi(root));

    return 0;
}

Program Çıktısı

çıktı
=== Trie Demo ===

--- Ekleme ---
  insert("araba")
  insert("ara")
  insert("arı")
  insert("bal")
  insert("bak")
  insert("balık")
  insert("can")
  insert("canan")
Toplam: 8 kelime

--- Arama ---
  search("ara")   → Bulundu ✓
  search("araba") → Bulundu ✓
  search("ar")    → Yok ✗        ← önek var ama kelime değil
  search("bal")   → Bulundu ✓
  search("bala")  → Yok ✗        ← bu kelime eklenmedi
  search("can")   → Bulundu ✓

--- Önek Arama ---
  startsWith("ar") → Var ✓       ← ara, araba, arı başlar
  startsWith("ba") → Var ✓       ← bak, bal, balık başlar
  startsWith("ca") → Var ✓       ← can, canan başlar
  startsWith("de") → Yok ✗       ← hiçbir kelime "de" ile başlamıyor

--- Otomatik Tamamlama ---
'ar' ile başlayanlar:
  → ara
  → araba
  → arı
'ba' ile başlayanlar:
  → bak
  → bal
  → balık
'c' ile başlayanlar:
  → can
  → canan

--- Silme ---
  delete("ara")
  search("ara")   → Yok ✗        ← silindi
  search("araba") → Var ✓        ← etkilenmedi
  delete("arı")

  Silme sonrası autocomplete("ar"):
'ar' ile başlayanlar:
  → araba                          ← sadece araba kaldı
Kalan: 6 kelime

Karmaşıklık Analizi

Trie operasyonlarının karmaşıklığı kelime uzunluğuna (m) bağlıdır, saklanan toplam kelime sayısından (n) bağımsızdır — bu en büyük avantajıdır.

OperasyonZamanAçıklama
Ekleme (insert)O(m)m karakter için m düğüm oluştur/ilerle
Arama (search)O(m)m karakter boyunca ilerle + isEnd kontrolü
Önek arama (startsWith)O(p)p = önek uzunluğu
Silme (delete)O(m)Rekürsif traversal + geriye temizlik
Otomatik tamamlamaO(p + k)p = önek, k = sonuç karakterleri toplamı
Tüm kelimeleri listeleO(N)N = tüm düğüm sayısı (DFS)

Trie vs Hash Table vs BST

ÖzellikTrieHash TableDengeli BST
Kelime aramaO(m)O(m) ort.O(m log n)
Önek aramaO(p) ✓Yapamaz ✗O(m log n)
Otomatik tamamlamaDoğal ✓Yapamaz ✗Karmaşık
Sıralı erişimDFS → sıralı ✓Sırasız ✗Inorder ✓
BellekYüksek (pointer dizi)DüşükOrta
En kötü aramaO(m) garantiO(n) çarpışmaO(m log n)
Bellek maliyeti: Her düğümde children[26] = 26 pointer = 26 × 8 byte = 208 byte (64-bit). Çoğu pointer NULL olsa bile yer kaplar. 10.000 kelimelik bir sözlük onbinlerce düğüm → megabyte'larca bellek. Bu sorunu çözmek için sıkıştırılmış varyantlar kullanılır.

Trie Varyantları

VaryantFarkAvantaj
Sıkıştırılmış Trie (Radix Tree)Tek çocuklu düğümleri birleştirir: a→r→a → "ara" tek kenarÇok daha az düğüm, daha az bellek
Ternary Search TrieHer düğümde 3 çocuk: less / equal / greaterBellek tasarrufu, BST+Trie karışımı
Suffix Trie / TreeBir string'in tüm soneklerini saklarAlt-string arama, biyoinformatik (DNA)
DAWGDirected Acyclic Word Graph — ortak sonekleri de paylaşırMinimum düğüm sayısı, kelime oyunları

Gerçek Dünya Kullanımları

UygulamaKullanım
Google AramaArama çubuğunda otomatik tamamlama / öneriler
IDE / EditörlerKod tamamlama (IntelliSense, autocomplete)
Yazım DenetimiSözlükte kelime var mı? Benzer kelimeler?
IP YönlendirmeLongest prefix match — ağ yönlendirme tabloları
Telefon Rehberiİsim/numara ile önek araması
Kelime OyunlarıScrabble — geçerli kelime kontrolü + kelime bulma
BiyoinformatikDNA dizisi eşleştirme (suffix tree)

Sonuç

Trie, string operasyonlarına özelleşmiş bir ağaç yapısıdır. Kelime uzunluğuna bağlı O(m) garanti arama — sözlükteki kelime sayısından bağımsız. Önek arama ve otomatik tamamlama gibi işlemler doğal olarak desteklenir — hash table ve BST bu konuda yetersiz kalır.

Bellek maliyeti dezavantajdır (her düğümde alfabe boyutu kadar pointer), ancak sıkıştırılmış varyantlar (radix tree, ternary search trie) bu sorunu büyük ölçüde çözer. Google aramasından DNA analizine, IP yönlendirmeden oyun motorlarına kadar geniş bir uygulama alanına sahiptir.

Bölüm 6.6 özeti: Trie = önek paylaşımlı ağaç. Her kenar bir karakter, kökten yol = önek. Düğümde children[ALFABE] + isEndOfWord. Ekleme/arama/silme O(m), m = kelime uzunluğu. Önek arama O(p). Otomatik tamamlama = önek bul + DFS → O(p + k). Hash table önek arayamaz, BST O(m log n). Bellek yüksek → sıkıştırılmış varyantlar (radix, ternary). Kullanım: arama motoru, IDE, yazım denetimi, IP yönlendirme, kelime oyunları.
← Veri Yapıları