Ders 24 / 25
Sonek Dizileri ve Ağaçları
Metni önişleyen yapılar, sonek dizisi kurulumu ve ikili aramayla sorgu, en uzun ortak önek dizisi, sonek ağacı ile karşılaştırma ve uygulama alanları.
İçindekiler
Önceki dört algoritma örüntüyü önişledi; metin her aramada baştan tarandı. Bu, tek seferlik aramalar için doğru dengedir.
Denge, aynı metinde binlerce sorgu yapıldığında tersine döner: metin bir kez önişlenip sorgular metin uzunluğuna değil örüntü uzunluğuna bağlı maliyetle yanıtlanabilir. Bu ders o yapıları kurar.
Sonek Dizisi
Bir metnin tüm sonekleri sıralanır ve yalnızca başlangıç konumları saklanır. Ortaya çıkan tam sayı dizisine sonek dizisi (suffix array) denir.
def sonek_dizisi(metin: str) -> list[int]: """Sonekleri sözlük sırasına dizen başlangıç konumları.""" return sorted(range(len(metin)), key=lambda i: metin[i:]) metin = "banana" sd = sonek_dizisi(metin) print(sd) # [5, 3, 1, 0, 4, 2] print([metin[i:] for i in sd]) # ['a', 'ana', 'anana', 'banana', 'na', 'nana']
Yapı yalnızca tam sayı tutar; metnin kendisi bir kez saklanır. Bellek maliyeti, sonekleri ayrı ayrı saklamaya göre çok düşüktür.
Yukarıdaki kurulum, karşılaştırma başına karakter okuduğu için ’dir ve yalnızca gösterim amaçlıdır. Sonek dizisi, uzunlukları ikişer katlayarak sıralayan yöntemle ’de, daha ileri yöntemlerle ’de kurulur.
Sorgu
Sonekler sıralı olduğuna göre, örüntüyle başlayan sonekler dizide bitişik bir aralıkta bulunur. İkili arama bu aralığın sınırlarını verir.
def aralik(metin: str, sd: list[int], oruntu: str) -> tuple[int, int]: """Örüntüyle başlayan soneklerin sonek dizisindeki [bas, son) aralığı.""" m = len(oruntu) lo, hi = 0, len(sd) while lo < hi: # alt sınır orta = (lo + hi) // 2 if metin[sd[orta]:sd[orta] + m] < oruntu: lo = orta + 1 else: hi = orta bas = lo lo, hi = bas, len(sd) while lo < hi: # üst sınır orta = (lo + hi) // 2 if metin[sd[orta]:sd[orta] + m] <= oruntu: lo = orta + 1 else: hi = orta return bas, lo uzun_metin = "ABABDABACDABABCABAB" sd_uzun = sonek_dizisi(uzun_metin) bas, son = aralik(uzun_metin, sd_uzun, "ABAB") print(son - bas, sorted(sd_uzun[bas:son])) # 3 [0, 10, 15] bas, son = aralik(uzun_metin, sd_uzun, "ZZ") print(son - bas) # 0
İkili aramanın her adımı karaktere kadar karşılaştırma yapar; sorgu maliyeti ’dir. Geçiş sayısı ise aralık genişliğinden tek işlemde okunur — kaç kez geçtiğini bulmak için metni taramak gerekmez.
İki maliyet karşılaştırılınca denge görünür: KMP her sorguda öder; sonek dizisi bir kez öder, sonra her sorguya ile yanıt verir. Sorgu sayısı büyüdükçe ikinci yol kazanır — Zaman ve Alan Ödünleşimi dersindeki önişleme kalıbının metin üzerindeki karşılığı.
En Uzun Ortak Önek Dizisi
Sonek dizisi tek başına eksiktir; komşu sonekler arasındaki örtüşme bilinirse çok daha fazla soru yanıtlanır.
LCP dizisi, sıralamada ardışık iki soneğin en uzun ortak önek uzunluğunu tutar. Kasai yöntemi bunu ’de hesaplar.
def lcp_dizisi(metin: str, sd: list[int]) -> list[int]: """lcp[k]: sd[k-1] ile sd[k] soneklerinin ortak önek uzunluğu.""" n = len(metin) sira = [0] * n for k, i in enumerate(sd): sira[i] = k lcp = [0] * n h = 0 for i in range(n): if sira[i] > 0: j = sd[sira[i] - 1] while i + h < n and j + h < n and metin[i + h] == metin[j + h]: h += 1 lcp[sira[i]] = h if h: h -= 1 # bir sonraki sonek en az h-1 ortak önek taşır else: h = 0 return lcp print(lcp_dizisi("banana", sd)) # [0, 1, 3, 0, 0, 2]
Dizinin en büyük değeri, metinde iki kez geçen en uzun parçayı verir.
def en_uzun_tekrar(metin: str) -> str: sd = sonek_dizisi(metin) lcp = lcp_dizisi(metin, sd) k = max(range(len(lcp)), key=lambda i: lcp[i]) return metin[sd[k]:sd[k] + lcp[k]] print(en_uzun_tekrar("banana")) # ana print(en_uzun_tekrar("ABABDABACDABABCABAB")) # ABAB
Aynı yapı başka soruları da yanıtlar: iki metnin en uzun ortak parçası (birleştirilip tek metin gibi işlenerek), farklı parça sayısı, ve sıkıştırmada tekrar tespiti.
Sonek Ağacı
Sonek ağacı, metnin tüm soneklerini içeren sıkıştırılmış bir sözlük ağacıdır: tek çocuklu düğüm zincirleri birleştirilir ve kenarlar metnin alt aralıklarını gösterir.
Sonek dizisiyle aynı bilgiyi taşır, farklı düzenler:
| Ölçüt | Sonek dizisi | Sonek ağacı |
|---|---|---|
| Kurulum | (ileri yöntemler) | |
| Sorgu | ||
| Bellek sabiti | Küçük ( tam sayı) | Büyük (düğüm ve işaretçiler) |
| Gerçekleştirim | Basit | Karmaşık |
Sorgu maliyetindeki logaritmik çarpan, sonek dizisine LCP bilgisi eklenerek de kaldırılabilir. Pratikte sonek dizisi tercih edilir: bellek sabiti küçük ve kodu daha denetlenebilirdir. Sonek ağacı, ağaç yapısının kendisini gerektiren kuramsal çözümlemelerde ve bazı özel sorgularda öne çıkar.
Aynı ailedeki üçüncü yapı sonek otomatıdır; metnin tüm alt dizgilerini tanıyan en küçük otomattır ve alt dizgi sayma gibi soruları doğrudan yanıtlar.
Nerede Kullanılır
Önişlemeli metin yapıları, metnin sabit ve sorguların çok olduğu her yerde görünür:
- Tam metin dizinleme. Belge kümesinde arama; sorgu başına metni taramak ölçeklenmez.
- Biyoenformatik. Genetik diziler uzun, abece küçük ve aynı dizi üzerinde binlerce sorgu yapılır.
- Yinelenen kod ve içerik tespiti. En uzun ortak parça sorgularıyla.
- Sıkıştırma. Tekrar eden parçaların bulunması; Burrows–Wheeler dönüşümü doğrudan sonek sıralamasına dayanır.
Ortak ölçüt aynıdır: önişleme maliyeti sorgu sayısına bölündüğünde kazanç kalıyor mu? Bu soru, Karmaşıklık Sınıflarını Okumak dersinde alt doğrusal sınıflar için sorulmuştu; yanıtı burada somut bir veri yapısı seçimine dönüşür.
Özet
- Sonek dizisi, metnin tüm soneklerini sözlük sırasına dizen başlangıç konumlarıdır ve tam sayı yer kaplar.
- Örüntüyle başlayan sonekler bitişik bir aralıktadır; ikili arama sorguyu ’de yanıtlar ve geçiş sayısını aralık genişliği verir.
- LCP dizisi ardışık soneklerin ortak önek uzunluğunu tutar ve ’de hesaplanır; en büyük değeri en uzun tekrar eden parçayı verir.
- Sonek ağacı aynı bilgiyi ağaç olarak düzenler; sorgusu ’dir ama bellek sabiti ve gerçekleştirim karmaşıklığı yüksektir.
- Yapı seçimi, önişleme maliyetinin sorgu sayısına bölünmesiyle verilir.
Sonraki Adım
Metin algoritmalarının son sorusu aramadan farklıdır: aynı bilgiyi daha az yerle saklamak. Sonraki ders, karakter sıklıklarına göre değişken uzunlukta kod üreten Huffman yöntemini kuracak ve açgözlü bir seçimin neden eniyi kodu verdiğini gösterecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.