Bölüm 5: Dizgi Algoritmaları

Algoritmalar
Kaba kuvvetKMP ve LPS tablosuBoyer-MooreRabin-Karp KarşılaştırmaRLELZW
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Kavramlar

Metin, örüntü, kayma, önek ve sonek kavramlarını tanımlamak; dizgi eşleştirme problemini biçimsel olarak ifade etmek.

Eşleştirme

Kaba kuvvet, KMP, Boyer-Moore ve Rabin-Karp algoritmalarını bir örnek üzerinde adım adım çalıştırmak ve Java ile yazmak.

Analiz

LPS tablosu, kötü karakter tablosu ve kayan hash'in zaman kazancını açıklamak; en iyi, ortalama ve en kötü durumları karşılaştırmak.

Sıkıştırma

RLE ve LZW ile kayıpsız sıkıştırma yapmak, sıkıştırılmış veriyi geri açmak ve sıkıştırma oranını hesaplamak.

Giriş

Dizgi Algoritmaları

  • Metinlerle dolu bir dünyada yaşıyoruz: e-postalar, mesajlar, sosyal medya paylaşımları, haber metinleri…
  • Bilgisayarlarımızda her gün sayısız metinle karşılaşıyoruz. Peki bu metinler nasıl düzenlenir ve analiz edilir?
  • Dizgi (string) algoritmaları metinlerde arama, değiştirme ve karşılaştırma gibi işlemleri gerçekleştirir.
  • Bu sunumun iki ana konusu: dizgi eşleştirme (bir örüntüyü metinde bulmak) ve dizgi sıkıştırma (metni daha az yerde saklamak).
Nerelerde?

Editörlerde Ctrl+F, grep, arama motorları, virüs imzası tarama, intihal tespiti, DNA dizilerinde gen arama, ZIP/GIF/PNG gibi dosya biçimleri.

Giriş

Temel Kavramlar ve Gösterim

  • Alfabe Σ: kullanılan karakterler kümesi (ör. {a, b, …, z}, {A, C, G, T}).
  • Metin (text) T[0..n−1] ve örüntü (pattern) P[0..m−1], genellikle m ≤ n.
  • P, T içinde s kaymasında (shift) geçer ⇔ T[s..s+m−1] = P. Geçerli kaymalar 0 ≤ s ≤ n − m.
  • Önek (prefix): P[0..k−1]; sonek (suffix): P[m−k..m−1]. Uygun (proper) önek/sonek, dizginin kendisine eşit olmayandır.
  • Örnek: "ababd" için önekler a, ab, aba, abab; sonekler d, bd, abd, babd.
metin Tababcababd0123456789örüntü Pababdkayma s = 5

Dizgi eşleştirme problemi: P'nin geçtiği tüm (ya da ilk) s değerlerini bulmak.

Giriş

Bölümün Haritası

Kaba Kuvvet (Brute Force)Metindeki her konum, örüntüyle eşleşip eşleşmediği için kontrol edilir. En fazla sayıda karşılaştırma gerektirebilir.
Knuth-Morris-Pratt (KMP)Başlangıçta bir tablo (LPS) oluşturur; metinde hiç geri gitmeden, karşılaştırma sayısını azaltarak hızlı çalışır.
Boyer-MooreSağdan sola karşılaştırır; kötü karakter ve iyi sonek kurallarıyla uzun atlamalar yapar. Uzun örüntülerde çok etkili.
Rabin-KarpHash (özet) değerlerini karşılaştırır; pencereyi kaydırırken hash'i O(1)'de günceller. Olasılıksal hızlıdır.
RLE (Run Length Encoding)Sıralı sıkıştırma kodlaması: ardışık tekrar eden değerler tek değer + tekrar sayısı olarak saklanır.
LZW (Lempel-Ziv-Welch)Sözlük tabanlı sıkıştırma: tekrar eden örüntüler dinamik olarak büyüyen bir sözlükteki kısa kodlarla temsil edilir (GIF).

Kaba Kuvvet

Kaba Kuvvet (Naive) Algoritması

  • Metin içinde belirli bir örüntüyü arayan en basit algoritmadır; "naive" (saf) adı bu yüzdendir.
  • İşleyiş:
    1. Her s = 0, 1, …, n − m kayması için örüntü metnin altına hizalanır.
    2. Karakterler soldan sağa karşılaştırılır; ilk uyuşmazlıkta durulur.
    3. m karakterin hepsi eşleşirse s rapor edilir; aksi hâlde örüntü 1 konum kaydırılır.
  • Ön işleme ve ek bellek gerektirmez.
Karmaşıklık

En kötü durum: (n − m + 1) · m karşılaştırma → O(n · m).

En iyi durum: her hizada ilk karakter uyuşmaz → O(n).

n: metin uzunluğu, m: örüntü uzunluğu.

Kaba Kuvvet

Örnek: "THIS IS A SIMPLE EXAMPLE"

THIS␣IS␣A␣SIMPLE␣EXAMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLESIMPLE

Her satır bir kayma: yeşil eşleşen, kırmızı uyuşmayan karşılaştırma. SIMPLE, 11. hizada (s = 10) bulunur; toplam 18 karşılaştırma yapılır (n = 24, m = 6).

Kaba Kuvvet · Kodu adım adım çalıştır

"HELLO WORLD" İçinde "LO": Kod İzleme

tHELLO␣WORLD012345678910

n = 11, m = 2. Örüntü i = 0, 1, …, 9 kaymalarında denenecek.

tHELLO␣WORLD012345678910pLOi = 0

t[0] = 'H' ≠ p[0] = 'L' → uyuşmazlık; örüntü 1 sağa kayar.

tHELLO␣WORLD012345678910pLOi = 1

t[1] = 'E' ≠ p[0] = 'L' → uyuşmazlık; örüntü 1 sağa kayar.

tHELLO␣WORLD012345678910pLOi = 2

t[2] = 'L' ile p[0] = 'L' eşleşti → j++.

tHELLO␣WORLD012345678910pLOi = 2

t[3] = 'L' ≠ p[1] = 'O' → uyuşmazlık; örüntü 1 sağa kayar.

tHELLO␣WORLD012345678910pLOi = 3

t[3] = 'L' ile p[0] = 'L' eşleşti → j++.

tHELLO␣WORLD012345678910pLOi = 3

t[4] = 'O' ile p[1] = 'O' eşleşti → j++.

tHELLO␣WORLD012345678910pLOi = 3

j == m: örüntü i = 3 konumunda bulundu. Toplam 6 karşılaştırma.

int kabaKuvvet(String t, String p) {
  int n = t.length(), m = p.length();
  for (int i = 0; i <= n - m; i++) {
    int j = 0;
    while (j < m &&
      t.charAt(i + j) == p.charAt(j))
      j++;
    if (j == m) return i;
  }
  return -1;
}

Kaba Kuvvet

Kaba Kuvvet: En İyi, Ortalama, En Kötü

DurumNe zaman?KarşılaştırmaZaman
En iyiP[0] metnin hiçbir karakteriyle eşleşmez (ör. P = "xyz", T = "aaaa…")n − m + 1O(n)
OrtalamaRastgele metin, büyük alfabe: uyuşmazlık genellikle ilk 1–2 karakterde≈ n · σ/(σ−1)O(n)
En kötüT = "aaaaaaaaaa", P = "aaab": her hizada m karşılaştırma(n − m + 1) · mO(n · m)
Sayısal örnek: n = 10, m = 4, T = a¹⁰, P = aaab → 7 hiza × 4 = 28 karşılaştırma. Uyuşmazlık hep son karakterde çıkar.
Asıl kusur: uyuşmazlıkta öğrenilen bilgi (eşleşen karakterler) atılır; metinde geri gidilip aynı karakterler tekrar okunur. KMP bu bilgiyi kullanır.

KMP

Knuth-Morris-Pratt (KMP) Algoritması

  • Metin içinde belirli bir örüntüyü bulmak için kullanılır.
  • Donald Knuth, Vaughan Pratt ve (bağımsız olarak) James H. Morris tarafından geliştirilmiş, 1977'de birlikte yayımlanmıştır.
  • Temel fikir: uyuşmazlık olduğunda örüntünün zaten eşleşmiş kısmı hakkında bildiklerimizi kullanmak; metin işaretçisi asla geri gitmez.
  • Bu bilgi örüntüden önceden hesaplanan LPS tablosunda (önek fonksiyonu, π) saklanır.
Karmaşıklık

Ön işleme: O(m)

Arama: O(n)

Toplam: O(n + m), en kötü durumda da

Ek bellek: O(m)

KMP

KMP: İşleyiş

1. Ön işlemeÖrüntünün her konumu için en uzun uygun önek = sonek (Longest Proper Prefix which is also Suffix, LPS) uzunluğu bir tabloya yazılır. Uyuşmazlıkta örüntüde nereye dönüleceğini bu tablo söyler.
2. Örüntü aramaMetin soldan sağa bir kez taranır. Eşleşmeyen karakterde örüntü işaretçisi q, tablodan okunan değere (q = lps[q − 1]) geri çekilir; metin işaretçisi i yerinde kalır.
3. Eşleşme kontrolüq, m'ye ulaştığında örüntünün tüm karakterleri eşleşmiştir: konum i − m + 1 rapor edilir. Tüm geçişler isteniyorsa q = lps[m − 1] ile devam edilir.
Neden doğru? P[0..q−1] metinle eşleşmişse ve P[q] uyuşmuyorsa, örüntünün bir sonraki olası hizası, P[0..q−1]'in hem öneki hem soneki olan en uzun parçanın üst üste geleceği hizadır. Arada kalan hizalar kesinlikle eşleşemez.

KMP

LPS Tablosu (Önek Fonksiyonu)

lps[q] = P[0..q] dizgisinin, kendisine eşit olmayan ve aynı zamanda soneki olan en uzun önekinin uzunluğu.

  • lps[0] = 0 (tek karakterin uygun öneki yok).
  • Örnek: Metin "ababcababcabababd", örüntü "ababd".
  • Örüntü tablosu: a: 0, b: 0, a: 1, b: 2, d: 0.
qP[q]lps[q]Gerekçe (P[0..q])
0a0"a" — uygun önek yok
1b0"ab" — a ≠ b
2a1"aba" — "a" hem önek hem sonek
3b2"abab" — "ab" hem önek hem sonek
4d0"ababd" — d ile biten önek yok

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

LPS Tablosunu Kurmak: "ACABACACD"

PlpsACABACACD0123456780

lps[0] = 0 her zaman. k = 0: şu an eşleşen önek uzunluğu.

PlpsACABACACD01234567800q

p[0] = 'A' ≠ p[1] = 'C' → k = 0, lps[1] = 0.

PlpsACABACACD012345678001qk

p[0] = 'A' = p[2] = 'A' → k = 1, lps[2] = 1.

PlpsACABACACD012345678001qk

p[1] = 'C' ≠ p[3] = 'B' → geri dön: k = lps[0] = 0.

PlpsACABACACD0123456780010q

p[0] = 'A' ≠ p[3] = 'B' → k = 0, lps[3] = 0.

PlpsACABACACD01234567800101qk

p[0] = 'A' = p[4] = 'A' → k = 1, lps[4] = 1.

PlpsACABACACD012345678001012qk

p[1] = 'C' = p[5] = 'C' → k = 2, lps[5] = 2. "AC" hem önek hem sonek.

PlpsACABACACD0123456780010123qk

p[2] = 'A' = p[6] = 'A' → k = 3, lps[6] = 3. "ACA" hem önek hem sonek.

PlpsACABACACD0123456780010123qk

p[3] = 'B' ≠ p[7] = 'C' → geri dön: k = lps[2] = 1.

PlpsACABACACD01234567800101232qk

p[1] = 'C' = p[7] = 'C' → k = 2, lps[7] = 2. "AC" hem önek hem sonek.

PlpsACABACACD01234567800101232qk

p[2] = 'A' ≠ p[8] = 'D' → geri dön: k = lps[1] = 0.

PlpsACABACACD012345678001012320q

p[0] = 'A' ≠ p[8] = 'D' → k = 0, lps[8] = 0.

PlpsACABACACD012345678001012320

Sonuç: lps = [0, 0, 1, 0, 1, 2, 3, 2, 0]. Her q için en fazla sabit sayıda iş (amortize) → O(m).

int[] lpsHesapla(String p) {
  int m = p.length(), k = 0;
  int[] lps = new int[m];
  for (int q = 1; q < m; q++) {
    while (k > 0 &&
      p.charAt(k) != p.charAt(q))
      k = lps[k - 1];
    if (p.charAt(k) == p.charAt(q))
      k++;
    lps[q] = k;
  }
  return lps;
}

KMP

KMP Aramasının Üç Evresi

Evre 1 · İlk karakteri araÖrüntünün ilk sembolüyle eşleşme bulunana kadar metin işaretçisi (i) ilerletilir.
Evre 2 · Birlikte ilerleSemboller eşleştikçe iki işaretçi de ilerler. Örüntünün sonuna ulaşılırsa (metin konumu − örüntü konumu) döndürülür.
Evre 3 · LPS ile geri çekilUyuşmazlıkta yalnızca örüntü işaretçisi (q) LPS ile geri çekilir; eşleşme ya da 0 konumuna ulaşılınca Evre 2'ye (q = 0 ise Evre 1'e) dönülür.
123eşleşmeuyuşmazlıkq>0q=0

KMP

KMP Örneği: "ABCDABD" Aranıyor

lps("ABCDABD") = [0, 0, 0, 0, 1, 2, 0]. yeşil eşleşen, kırmızı uyuşmayan, mavi LPS sayesinde bilinen (tekrar karşılaştırılmayan) karakterler.

ABCABCDABABCDABCDABDE01234567891011121314151617181920ABCDABD

s = 0: ABC eşleşti, t[3] = 'A' ≠ p[3] = 'D'. q = 3 → q = lps[2] = 0; örüntü 3 kayar, t[3] yeniden p[0] ile karşılaştırılır.

ABCABCDABABCDABCDABDE01234567891011121314151617181920ABCDABD

s = 3: ABCDAB eşleşti, t[9] = 'A' ≠ 'D'. q = 6 → q = lps[5] = 2: "AB" zaten eşleşmiş bilinir, örüntü 4 kayar.

ABCABCDABABCDABCDABDE01234567891011121314151617181920ABCDABD

s = 7: AB yeniden karşılaştırılmaz. t[9] = 'A' ≠ p[2] = 'C' → q = lps[1] = 0.

ABCABCDABABCDABCDABDE01234567891011121314151617181920ABCDABD

s = 9: ABCDAB eşleşti, t[15] = 'C' ≠ 'D'. q = 6 → q = lps[5] = 2; örüntü 4 kayar.

ABCABCDABABCDABCDABDE01234567891011121314151617181920ABCDABD

s = 13: AB bilinir; CDABD da eşleşir → örüntü 13. konumda bulundu. Metin işaretçisi hiç geri gitmedi.

Toplam 24 karakter karşılaştırması (n = 21); kaba kuvvet aynı metinde 37 karşılaştırma yapar.

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

kmpAra("ababcababcabababd", "ababd")

tababcababcabababd012345678910111213141516lps = [0, 0, 1, 2, 0]

n = 17, m = 5, lps = [0, 0, 1, 2, 0], q = 0.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[0] = t[0] = 'a' → q = 1.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[1] = t[1] = 'b' → q = 2.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = t[2] = 'a' → q = 3.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[3] = t[3] = 'b' → q = 4.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[4] = 'd' ≠ t[4] = 'c' → q = lps[3] = 2 (i yerinde kalır).

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = 'a' ≠ t[4] = 'c' → q = lps[1] = 0 (i yerinde kalır).

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[0] = 'a' ≠ t[4] = 'c' → q = 0 kalır, i ilerler.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[0] = t[5] = 'a' → q = 1.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[1] = t[6] = 'b' → q = 2.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = t[7] = 'a' → q = 3.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[3] = t[8] = 'b' → q = 4.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[4] = 'd' ≠ t[9] = 'c' → q = lps[3] = 2 (i yerinde kalır).

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = 'a' ≠ t[9] = 'c' → q = lps[1] = 0 (i yerinde kalır).

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[0] = 'a' ≠ t[9] = 'c' → q = 0 kalır, i ilerler.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[0] = t[10] = 'a' → q = 1.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[1] = t[11] = 'b' → q = 2.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = t[12] = 'a' → q = 3.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[3] = t[13] = 'b' → q = 4.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[4] = 'd' ≠ t[14] = 'a' → q = lps[3] = 2 (i yerinde kalır).

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[2] = t[14] = 'a' → q = 3.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[3] = t[15] = 'b' → q = 4.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

p[4] = t[16] = 'd' → q = 5.

tababcababcabababd012345678910111213141516pababdilps = [0, 0, 1, 2, 0]

q == m → i − m + 1 = 12 döndürülür. Metin yalnızca bir kez tarandı.

int kmpAra(String t, String p) {
  int n = t.length(), m = p.length();
  int[] lps = lpsHesapla(p);
  int q = 0;  // eşleşen uzunluk
  for (int i = 0; i < n; i++) {
    while (q > 0 &&
      p.charAt(q) != t.charAt(i))
      q = lps[q - 1];
    if (p.charAt(q) == t.charAt(i))
      q++;
    if (q == m)
      return i - m + 1;
  }
  return -1;
}

KMP

KMP: Neden O(n + m)?

  • Dış döngü i'yi n kez artırır; i hiç azalmaz.
  • q her adımda en fazla 1 artar → toplam artış ≤ n.
  • while içindeki her geri çekilme q'yu en az 1 azaltır; q negatif olamaz → toplam geri çekilme ≤ toplam artış ≤ n.
  • Arama: en fazla 2n karşılaştırma → O(n). Aynı amortize argüman LPS için O(m) verir.

Artıları

  • En kötü durumda bile doğrusal
  • Metinde geri gitmez: akış (stream) verisine uygun

Eksileri

  • Pratikte Boyer-Moore'dan genelde yavaş
  • Küçük alfabede (DNA) atlama kazancı sınırlı

Boyer-Moore

Boyer-Moore Algoritması

  • Metin içinde belirli bir örüntüyü arar; Robert S. Boyer ve J Strother Moore tarafından 1977'de geliştirilmiştir.
  • Örüntü soldan sağa kaydırılır ama karakterler sağdan sola karşılaştırılır.
  • Uyuşmazlıkta iki kuraldan büyük olan kadar kaydırılır: kötü karakter ve iyi sonek.
  • Metindeki karakterlerin çoğu hiç okunmadan atlanabilir: uzun örüntü ve geniş alfabede (doğal dil metni) çok hızlıdır; grep ve editörlerin aramaları bu ailedendir.
Karmaşıklık

En iyi: O(n/m) — her denemede ilk karşılaştırma uyuşmaz ve m atlanır.

Ortalama: alt-doğrusal, pratikte O(n)'den hızlı.

En kötü: yalnız kötü karakterle O(n · m); iyi sonek + Galil kuralıyla O(n + m).

Boyer-Moore

Boyer-Moore: İşleyiş

  1. Ön işleme: örüntüden kaydırma tabloları kurulur: her karakterin örüntüdeki son görülme konumu (kötü karakter) ve her sonek için kaydırma miktarı (iyi sonek).
  2. Arama: örüntü metnin altına hizalanır; karşılaştırma örüntünün son karakterinden başlayıp sola doğru ilerler.
  3. Eşleşme kontrolü: tüm karakterler eşleşirse konum rapor edilir.
  4. Kötü karakter kuralı: uyuşmayan metin karakterinin örüntüdeki en sağdaki konumu dikkate alınarak kaydırma yapılır.
  5. İyi sonek kuralı: eşleşmiş sonekin örüntü içinde başka bir yerde bulunup bulunmadığına göre kaydırma yapılır.
Kaydırma = max(kötü karakter kaydırması, iyi sonek kaydırması). İkisi de "güvenli"dir: atlanan hiçbir hizada eşleşme olamaz.

Boyer-Moore

Kötü Karakter (Bad Character) Kuralı

örüntüabacab012345a4b5c3*−1son görülme tablosu
  • p[j] ≠ t[s + j] olsun; c = t[s + j] kötü karakter.
  • c örüntüde j'nin solunda geçiyorsa: en sağdaki c, t[s + j]'nin altına gelecek şekilde kaydır.
  • c örüntüde hiç yoksa (son = −1): örüntüyü c'nin tamamen ötesine kaydır (j + 1).
  • Formül: s += max(1, j − son[c]). c, j'nin sağında son kez geçiyorsa j − son[c] ≤ 0 olur; bu yüzden en az 1.
int[] sonGorulme(String p) {
  int[] son = new int[256];
  Arrays.fill(son, -1);
  for (int i = 0; i < p.length(); i++)
    son[p.charAt(i)] = i;
  return son;
}

Boyer-Moore

İyi Sonek (Good Suffix) Kuralı

  • Uyuşmazlıktan önce eşleşen sonek u = P[j+1..m−1] metinde bulunmuştur: "iyi sonek".
  • Durum 1: u örüntüde başka yerde (önünde farklı bir karakterle) geçiyorsa, o geçiş metindeki u'nun altına gelecek şekilde kaydır.
  • Durum 2: yoksa, u'nun bir sonekine eşit olan en uzun P önekini hizala.
  • Durum 3: hiçbiri yoksa m kaydır.
  • Tablo O(m) zamanda önceden hesaplanır (KMP'nin LPS fikrine benzer, sonekler için).
tBBAABCABAB0123456789önceCABABsonraCABAB

"AB" eşleşti, t[2] = 'A' ≠ p[2] = 'B'. Kötü karakter: son[A] = 3 > j → yalnız 1. İyi sonek: "AB" p[1..2]'de de var (önünde 'C' ≠ 'B') → 2 kaydır.

Boyer-Moore

Örnek: "LO LOELLO O HELLO" İçinde "HELLO"

cHELOdiğer
son[c]0134−1

Kural: s += max(1, j − son[c]). yeşil eşleşen, kırmızı kötü karakter.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 1 (s = 0): 2 karakter eşleşti, t[2] = '␣' ≠ p[2] = 'L'. '␣' örüntüde yok → j − (−1) = 3 → 3 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 2 (s = 3): t[7] = 'L' ≠ p[4] = 'O'. son['L'] = 3 → j − 3 = 1 → 1 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 3 (s = 4): 4 karakter eşleşti, t[4] = 'O' ≠ p[0] = 'H'. son['O'] = 4 ≥ j → en az 1 → 1 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 4 (s = 5): t[9] = '␣' ≠ p[4] = 'O'. '␣' örüntüde yok → j − (−1) = 5 → 5 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 5 (s = 10): t[14] = 'L' ≠ p[4] = 'O'. son['L'] = 3 → j − 3 = 1 → 1 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 6 (s = 11): t[15] = 'L' ≠ p[4] = 'O'. son['L'] = 3 → j − 3 = 1 → 1 kaydır.

LO␣LOELLO␣O␣HELLO012345678910111213141516HELLO

Hiza 7 (s = 12): 5 karakterin hepsi sağdan sola eşleşti → HELLO, s = 12'de bulundu. Toplam 7 hiza, 17 karşılaştırma.

Boyer-Moore

Horspool Basitleştirmesi ve Kaydırma Tablosu

  • Horspool (1980): uyuşmazlık nerede olursa olsun, kaydırmayı hizanın son karakterinin altındaki metin karakterine göre yap.
  • Kaydırma tablosu: kayd[c] = m − 1 − (c'nin P[0..m−2] içindeki en sağ konumu); yoksa m.
  • HELLO: H → 4, E → 3, L → 1, O → 5 (son karakter tabloya katılmaz; O başka yerde yok), diğer → 5.
  • Aynı örnekte 5 hiza yeter (temel kötü karakter kuralıyla 7).
HizasKarşılaştırmason kar.kaydır
10t[2] = '␣' ≠ p[2]'O'5
25t[9] = '␣' ≠ p[4]'␣'5
310t[14] = 'L' ≠ p[4]'L'1
411t[15] = 'L' ≠ p[4]'L'1
512tümü eşleşti—bulundu

Boyer-Moore

Örnek: Son Görülme Tablosu ile Arama

Örüntü abacab, son görülme: a = 4, b = 5, c = 3, * = −1. Hücre üstündeki sayı: karşılaştırma sayacı.

abacaabadcabacabaabb0123456789101112131415161718191abacab

Hiza 1 (s = 0): t[5] = 'a' ≠ p[5] = 'b'. j − son['a'] = 5 − 4 = 1 → 1 kaydır.

abacaabadcabacabaabb012345678910111213141516171819234abacab

Hiza 2 (s = 1): t[4] = 'a' ≠ p[3] = 'c'. son['a'] = 4 ≥ j = 3 → 1 → 1 kaydır.

abacaabadcabacabaabb0123456789101112131415161718195abacab

Hiza 3 (s = 2): t[7] = 'a' ≠ p[5] = 'b'. j − son['a'] = 5 − 4 = 1 → 1 kaydır.

abacaabadcabacabaabb0123456789101112131415161718196abacab

Hiza 4 (s = 3): t[8] = 'd' ≠ p[5] = 'b'. 'd' örüntüde yok → j + 1 = 6 → 6 kaydır.

abacaabadcabacabaabb0123456789101112131415161718197abacab

Hiza 5 (s = 9): t[14] = 'a' ≠ p[5] = 'b'. j − son['a'] = 5 − 4 = 1 → 1 kaydır.

abacaabadcabacabaabb0123456789101112131415161718198910111213abacab

Hiza 6 (s = 10): 6 karşılaştırmanın hepsi eşleşti → eşleşme, s = 10. Tüm geçişler arandığı için 1 kaydırılıp devam edilir.

abacaabadcabacabaabb01234567891011121314151617181914abacab

Hiza 7 (s = 11): t[16] = 'a' ≠ p[5] = 'b'. j − son['a'] = 5 − 4 = 1 → 1 kaydır.

abacaabadcabacabaabb01234567891011121314151617181915abacab

Hiza 8 (s = 12): t[17] = 'a' ≠ p[5] = 'b'. j − son['a'] = 5 − 4 = 1 → 1 kaydır.

abacaabadcabacabaabb012345678910111213141516171819161718abacab

Hiza 9 (s = 13): t[16] = 'a' ≠ p[3] = 'c'. son['a'] = 4 ≥ j = 3 → 1 → 1 kaydır.

abacaabadcabacabaabb0123456789101112131415161718191920abacab

Hiza 10 (s = 14): t[18] = 'b' ≠ p[4] = 'a'. son['b'] = 5 ≥ j = 4 → 1 → 1 kaydır.

10 hiza, toplam 20 karşılaştırma (n = 20, m = 6): 'd' gibi örüntüde olmayan bir karakter 6 konumluk atlama sağlar.

Boyer-Moore · Kodu adım adım çalıştır

boyerMoore("merhaba naber", "abe")

tmerhaba␣naber0123456789101112son: a = 0, b = 1, e = 2, diğer = −1

n = 13, m = 3. Son görülme tablosu kuruldu; s = 0.

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[2] = 'e' ≠ t[2] = 'r'; son['r'] = -1 → s += max(1, 2 − (-1)) = 3.

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[2] = 'e' ≠ t[5] = 'b'; son['b'] = 1 → s += max(1, 2 − (1)) = 1.

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[2] = 'e' ≠ t[6] = 'a'; son['a'] = 0 → s += max(1, 2 − (0)) = 2.

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[2] = 'e' ≠ t[8] = 'n'; son['n'] = -1 → s += max(1, 2 − (-1)) = 3.

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[2] = t[11] = 'e' → j-- .

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[1] = t[10] = 'b' → j-- .

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

p[0] = t[9] = 'a' → j-- .

tmerhaba␣naber0123456789101112pabesson: a = 0, b = 1, e = 2, diğer = −1

j < 0 → örüntü s = 9 konumunda bulundu ("naber").

int boyerMoore(String t, String p) {
  int n = t.length(), m = p.length();
  int[] son = sonGorulme(p);
  int s = 0;
  while (s <= n - m) {
    int j = m - 1;
    while (j >= 0 &&
      p.charAt(j) == t.charAt(s + j))
      j--;
    if (j < 0) return s;
    s += Math.max(1,
      j - son[t.charAt(s + j)]);
  }
  return -1;
}

Boyer-Moore

Boyer-Moore: Analiz ve Örnek

  • En iyi: metin karakterleri örüntüde yok → her hizada 1 karşılaştırma, m atlama: ≈ n/m karşılaştırma.
  • En kötü (yalnız kötü karakter): T = aaaa…a, P = baaa…a → her hizada m karşılaştırma, 1 kaydırma: O(n · m).
  • Örüntü uzadıkça hızlanır (kaba kuvvet ve KMP'nin tersine).
  • Küçük alfabede (DNA, ikili veri) kötü karakter kuralı zayıftır; iyi sonek kuralı önem kazanır.
Örnek

Metin: abccbaabccbaabcbcabbabcabc

Desen: abcbcabbabcabc (m = 14)

Son görülme tablosu: a: 11, b: 12, c: 13

Sonuç: desen 12. konumda bulunur.

Rabin-Karp

Rabin-Karp Algoritması

  • Metin içinde belirli bir örüntüyü bulmak için kullanılır; Michael O. Rabin ve Richard M. Karp tarafından 1987'de geliştirilmiştir.
  • Karakterleri tek tek karşılaştırmak yerine örüntünün ve her m uzunluklu metin penceresinin hash (özet) değerini karşılaştırır.
  • Kayan hash (rolling hash): pencere bir sağa kayınca yeni hash, eskisinden O(1)'de hesaplanır.
  • Hash eşitliği eşleşmeyi garanti etmez: sahte eşleşme (spurious hit) olabilir → karakter bazında doğrulama gerekir.
Karmaşıklık

En iyi: hash'ler nadiren çakışır → O(n + m)

Ortalama: pratikte O(n + m)

En kötü: her pencerede çakışma (sahte eşleşme), sürekli karakter kontrolü → O(n · m)

Rabin-Karp

Rabin-Karp: İşleyiş ve Kayan Hash

  1. Hash: örüntünün ve metnin ilk penceresinin hash değerleri hesaplanır.
  2. Eşleşme kontrolü: hash değerleri eşleşen alt dizgeler aday kabul edilir.
  3. Doğrulama: aday pencere karakter bazında örüntüyle karşılaştırılır.
  4. Kaydırma ve yeniden hesaplama: eşleşme yoksa pencere bir sağa kaydırılır, hash güncellenir.
  5. Tekrarlama: tüm metin boyunca adımlar tekrarlanır.
Polinom hash (taban B, asal mod Q):
h(t[s..s+m−1]) = (t[s]·Bm−1 + t[s+1]·Bm−2 + … + t[s+m−1]) mod Q
Kaydırma: hs+1 = (B · (hs − t[s] · Bm−1) + t[s+m]) mod Q — çıkan karakter düşülür, kalanlar bir basamak sola kayar, giren karakter eklenir.

Rabin-Karp

Basit Örnek: Toplam Hash ve Sahte Eşleşme

Oyuncak hash: harf değerlerinin toplamı. "VUATS" metninde "TS" aranıyor.

VUATS5 + 1 = 6örüntü "TS"h = 2 + 3 = 5U=1 T=2 S=3 A=4 V=5

Pencere "VU": 5 + 1 = 6 ≠ 5 → hash farklı, karakter karşılaştırması yapılmaz.

VUATS1 + 4 = 5örüntü "TS"h = 2 + 3 = 5U=1 T=2 S=3 A=4 V=5

Pencere "UA": 1 + 4 = 5 = 5 → hash eşit ama "UA" ≠ "TS": sahte eşleşme (spurious hit); doğrulama boşa gider.

VUATS4 + 2 = 6örüntü "TS"h = 2 + 3 = 5U=1 T=2 S=3 A=4 V=5

Pencere "AT": 4 + 2 = 6 ≠ 5 → geç. Yeni hash = eski − çıkan (U=1) + giren (T=2): O(1).

VUATS2 + 3 = 5örüntü "TS"h = 2 + 3 = 5U=1 T=2 S=3 A=4 V=5

Pencere "TS": 2 + 3 = 5 → hash eşit, karakter karşılaştırması da tutar: eşleşme (s = 3).

Toplam hash zayıftır (harflerin sırasına duyarsız: "ST" de 5 verir). Bu yüzden konum ağırlıklı polinom hash ve büyük asal Q kullanılır.

Rabin-Karp · Kodu adım adım çalıştır

rabinKarp("abracadabra", "cad")

tabracadabra012345678910a=97 b=98 c=99 d=100 r=114

n = 11, m = 3, B = 256, Q = 101. h = 256² mod 101 = 88 (çıkan karakterin ağırlığı).

tabracadabra012345678910pencere s = 0ph = h("cad") = 11, th = h("abr") = 4

ph = (99·256² + 97·256 + 100) mod 101 = 11; ilk pencere "abr" için th = 4.

tabracadabra012345678910pencere s = 0th("abr") = 4 ≠ ph = 11

Pencere "abr": th = 4 ≠ 11 → karakterlere bakılmaz. Kaydır: 'a' çıkar, 'a' girer → yeni th = 30.

tabracadabra012345678910pencere s = 1th("bra") = 30 ≠ ph = 11

Pencere "bra": th = 30 ≠ 11 → karakterlere bakılmaz. Kaydır: 'b' çıkar, 'c' girer → yeni th = 17.

tabracadabra012345678910pencere s = 2th("rac") = 17 ≠ ph = 11

Pencere "rac": th = 17 ≠ 11 → karakterlere bakılmaz. Kaydır: 'r' çıkar, 'a' girer → yeni th = 41.

tabracadabra012345678910pencere s = 3th("aca") = 41 ≠ ph = 11

Pencere "aca": th = 41 ≠ 11 → karakterlere bakılmaz. Kaydır: 'a' çıkar, 'd' girer → yeni th = 11.

tabracadabra012345678910pencere s = 4th = 11 = ph → karakter karşılaştırması

th = 11 = ph → esit karakter karşılaştırması: "cad" = "cad" → s = 4 döndürülür.

int rabinKarp(String t, String p) {
  int n = t.length(), m = p.length();
  int B = 256, Q = 101, h = 1;
  for (int i = 0; i < m - 1; i++)
    h = (h * B) % Q;  // B^(m-1) mod Q
  int ph = 0, th = 0;
  for (int i = 0; i < m; i++) {
    ph = (B * ph + p.charAt(i)) % Q;
    th = (B * th + t.charAt(i)) % Q;
  }
  for (int s = 0; s <= n - m; s++) {
    if (ph == th && esit(t, p, s))
      return s;
    if (s < n - m) {
      th = (B * (th - t.charAt(s) * h)
            + t.charAt(s + m)) % Q;
      if (th < 0) th += Q;
    }
  }
  return -1;
}

Rabin-Karp

Kayan Pencere ve Analiz

İndis 0abcdefabcİndis 1abcdefabcİndis 2abcdefabcİndis 3abcdefabcİndis 4abcdefabcİndis 5abcdefabcİndis 6abcdefabc

n − m + 1 = 7 pencere; her biri bir öncekinden O(1)'de.

  • Hash hesapları: O(m) + (n − m) · O(1).
  • Q büyük bir asal seçilirse sahte eşleşme olasılığı ≈ 1/Q → beklenen süre O(n + m).
  • Kötü Q veya kötü niyetli girdi: her pencere aday → O(n · m).
  • Güçlü yanı: aynı uzunlukta çok sayıda örüntü tek geçişte aranabilir (hash'ler bir kümeye konur): intihal tespiti, belge benzerliği.
  • Ayrıca 2B örüntü (görüntü içinde blok) aramaya genellenebilir.

Eşleştirme Özeti

Dizgi Eşleştirme Algoritmalarının Karşılaştırması

AlgoritmaTemel fikirÖn işlemeOrtalamaEn kötüEk bellek
Kaba kuvvetHer konumu deneryokO(n)*O(n · m)O(1)
KMPLPS tablosu, metinde geri gitmezO(m)O(n + m)O(n + m)O(m)
Boyer-MooreSağdan sola, kötü karakter + iyi sonekO(m + σ)alt-doğrusalO(n · m)**O(m + σ)
Rabin-KarpKayan hash karşılaştırmasıO(m)O(n + m)O(n · m)O(1)

* rastgele metin ve geniş alfabe için; düzenli (tekrarlı) metinde O(n · m)'ye yaklaşır. ** yalnız kötü karakter kuralıyla; iyi sonek + Galil kuralıyla O(n + m). σ: alfabe boyutu.

Garanti gerekiyorsaKMP: akış verisi, en kötü durum sınırı önemli.
Uzun örüntü, doğal dilBoyer-Moore / Horspool: pratikte en hızlısı.
Çok sayıda örüntüRabin-Karp: tek geçişte birçok örüntü.

Sıkıştırma

Dizgi Sıkıştırma Algoritmaları

  • Amaç: metni (veya veriyi) daha az bitle saklamak/iletmek.
  • Kayıpsız (lossless) sıkıştırma: açıldığında orijinal veri aynen elde edilir. RLE ve LZW kayıpsızdır (metin, program, PNG/GIF).
  • Kayıplı (lossy) sıkıştırma: algıda önemsiz ayrıntılar atılır (JPEG, MP3); metin için uygun değildir.
  • Sıkıştırma oranı = sıkıştırılmış boyut / orijinal boyut (küçük olması iyi).
RLEAynı değerlerin ardışık tekrarları tek değer + sayı olarak saklanır.
LZWTekrar eden örüntüler, dinamik büyüyen bir sözlükteki kısa kodlarla temsil edilir.

RLE

Sıralı Sıkıştırma Kodlaması (RLE)

  • Run Length Encoding: ardışık tekrar eden karakterleri (koşu, run) sayı + karakter çiftiyle değiştirir.
  • Adımlar: metin soldan sağa taranır → ardışık tekrarlar sayılır → sayı ve karakter birleştirilir → sıkıştırılmış metin olarak saklanır.
  • Metin tek bir kez tarandığı için zaman O(n).
  • Basit ve etkili; ancak tekrar az ise etkisiz, hatta zararlıdır: "ABCD" → "1A1B1C1D" (iki kat!).
  • Kullanım: faks (siyah-beyaz satırlar), BMP/PCX/TIFF, basit simgeler.
Örnek

Metin: AAAAABBBCCCCDDD (15 karakter)

Sıkıştırılmış: 5A3B4C3D (8 karakter)

Oran: 8/15 ≈ 0,53

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

rleSikistir("AAAAABBBCCCCDDD")

AAAAABBBCCCCDDD01234567891011121314

n = 15, sonuc boş.

AAAAABBBCCCCDDD01234567891011121314i

Yeni koşu: c = 'A', sayac = 1 (i = 0).

AAAAABBBCCCCDDD01234567891011121314i

while döngüsü 4 kez döndü: sayac = 5, i = 4. sonuc'a "5A" eklenir.

AAAAABBBCCCCDDD01234567891011121314i

Yeni koşu: c = 'B', sayac = 1 (i = 5).

AAAAABBBCCCCDDD01234567891011121314i

while döngüsü 2 kez döndü: sayac = 3, i = 7. sonuc'a "3B" eklenir.

AAAAABBBCCCCDDD01234567891011121314i

Yeni koşu: c = 'C', sayac = 1 (i = 8).

AAAAABBBCCCCDDD01234567891011121314i

while döngüsü 3 kez döndü: sayac = 4, i = 11. sonuc'a "4C" eklenir.

AAAAABBBCCCCDDD01234567891011121314i

Yeni koşu: c = 'D', sayac = 1 (i = 12).

AAAAABBBCCCCDDD01234567891011121314i

while döngüsü 2 kez döndü: sayac = 3, i = 14. sonuc'a "3D" eklenir.

AAAAABBBCCCCDDD01234567891011121314

Sonuç: "5A3B4C3D" — 15 karakter yerine 8.

String rleSikistir(String s) {
  StringBuilder sonuc = new StringBuilder();
  int n = s.length();
  for (int i = 0; i < n; i++) {
    char c = s.charAt(i);
    int sayac = 1;
    while (i + 1 < n &&
      s.charAt(i + 1) == c) {
      sayac++;
      i++;
    }
    sonuc.append(sayac).append(c);
  }
  return sonuc.toString();
}

RLE

RLE'nin Bayt Düzeyinde Kodlanması

Metin: aaaaaaaaaabbbbbbececececececdddddddddddddddecb (46 bayt)

Sayı–karakter çiftleri(10, a)(6, b)(1, e)(1, c)(1, e)(1, c)(1, e)(1, c)(1, e)(1, c)(1, e)(1, c)(1, e)(1, c)(15, d)(1, e)(1, c)(1, b)
Bayraksız RLE
(her koşu 2 bayt)
10 97 06 98 01 101 01 99 01 101 01 99 01 101 01 99 01 101 01 99 01 101 01 99 01 101 01 99 15 100 01 101 01 99 01 98 → 36 bayt
Bayraklı RLE
(bayrak = 255)
255 10 97 255 06 98 101 99 101 99 101 99 101 99 101 99 101 99 255 15 100 101 99 98 → 24 bayt
Bayraksız RLE'de "ececec…" gibi tekrarsız bölgeler her karakteri 2 bayta çıkarır. Bayraklı RLE yalnızca uzun koşuları 255 sayı karakter üçlüsüyle yazar, diğer karakterleri olduğu gibi bırakır (255 değerinin kendisi de kaçış ile yazılmalıdır).

RLE

İkili (Siyah-Beyaz) Görüntüde RLE

W1B1W3B1W1B3W1B3B7W1B5W1W2B3W2W3B1W3
  • Her satır soldan sağa taranır: W = beyaz, B = siyah, ardından koşu uzunluğu.
  • 42 piksel → 18 koşu (her satır ayrı kodlanır). Yalnızca iki renk olduğu için renk adı bile yazılmayabilir: koşular dönüşümlü olarak beyaz/siyah kabul edilir (faks standardı bu fikri kullanır).
  • Büyük düz alanlı görüntülerde (çizim, metin taraması) çok etkilidir; fotoğraflarda etkisizdir.

RLE

Piksel Düzeyinde RLE (RGB)

Mavi (0, 0, 255), siyah (0, 0, 0), sarı (255, 255, 0). Ham: 64 × 3 = 192 bayt. RLE: 33 koşu × (1 sayı + 3 renk) = 132 bayt.

SayıR G BSayıR G BSayıR G B
20 0 25540 0 030 0 255
10 0 04255 255 010 0 0
10 0 25510 0 01255 255 0
10 0 02255 255 010 0 0
1255 255 020 0 06255 255 0
20 0 01255 255 010 0 0
2255 255 010 0 01255 255 0
20 0 02255 255 020 0 0
2255 255 010 0 010 0 255
10 0 04255 255 010 0 0
30 0 25540 0 020 0 255

Tablo satır satır (soldan sağa, sonra alt satır) okunur; satır sonunda aynı renk devam ediyorsa koşu bir sonraki satıra taşar (ör. 3 mavi).

LZW

Lempel-Ziv-Welch (LZW) Sıkıştırma

  • Sözlük tabanlı kayıpsız sıkıştırma: Lempel ve Ziv'in LZ78 yöntemini (1978) Terry Welch geliştirmiştir (1984).
  • Tekrar eden dizgileri tanımlar ve onları sözlükteki kısa kodlarla (indeks) temsil eder.
  • Sözlük dinamikdir: sıkıştırırken büyür; gönderilmez, açıcı aynı sözlüğü kendisi yeniden kurar.
  • GIF, TIFF, PDF ve Unix compress gibi biçimlerde kullanılır.
  • Sözlük büyüdükçe kod genişliği (bit) ve bellek artar, performans düşer: pratikte sözlük sınırlanır (GIF: 4096 kod = 12 bit) ve dolunca sıfırlanır.
Örnek

Metin: ABABCABABCA

Başlangıç sözlüğü: A = 0, B = 1, C = 2

Sıkıştırılmış: 0 1 3 2 3 5 0

11 sembol yerine 7 kod.

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

lzw("ABABCABABCA")

ABABCABABCA0123456789100A1B2C

Sözlük metindeki karakterlerle başlar: A = 0, B = 1, C = 2. w = "".

ABABCABABCA012345678910c0A1B2C

"A" sözlükte (0) → w = "A"; daha uzun bir eşleşme aranır.

ABABCABABCA012345678910c0A1B2C3AB

"AB" sözlükte yok → w = "A" kodu 0 çıktıya; "AB" = 3 sözlüğe eklenir; w = "B".

ABABCABABCA012345678910c0A1B2C3AB4BA

"BA" sözlükte yok → w = "B" kodu 1 çıktıya; "BA" = 4 sözlüğe eklenir; w = "A".

ABABCABABCA012345678910c0A1B2C3AB4BA

"AB" sözlükte (3) → w = "AB"; daha uzun bir eşleşme aranır.

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC

"ABC" sözlükte yok → w = "AB" kodu 3 çıktıya; "ABC" = 5 sözlüğe eklenir; w = "C".

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA

"CA" sözlükte yok → w = "C" kodu 2 çıktıya; "CA" = 6 sözlüğe eklenir; w = "A".

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA

"AB" sözlükte (3) → w = "AB"; daha uzun bir eşleşme aranır.

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA7ABA

"ABA" sözlükte yok → w = "AB" kodu 3 çıktıya; "ABA" = 7 sözlüğe eklenir; w = "A".

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA7ABA

"AB" sözlükte (3) → w = "AB"; daha uzun bir eşleşme aranır.

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA7ABA

"ABC" sözlükte (5) → w = "ABC"; daha uzun bir eşleşme aranır.

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA7ABA8ABCA

"ABCA" sözlükte yok → w = "ABC" kodu 5 çıktıya; "ABCA" = 8 sözlüğe eklenir; w = "A".

ABABCABABCA012345678910c0A1B2C3AB4BA5ABC6CA7ABA8ABCA

Döngü bitti: kalan w = "A" kodu 0 eklenir. Çıktı: 0 1 3 2 3 5 0.

List<Integer> lzw(String s) {
  Map<String, Integer> sozluk =
      ilkSozluk(s);  // A=0, B=1, C=2
  String w = "";
  List<Integer> kod = new ArrayList<>();
  for (char c : s.toCharArray()) {
    String wc = w + c;
    if (sozluk.containsKey(wc)) {
      w = wc;
    } else {
      kod.add(sozluk.get(w));
      sozluk.put(wc, sozluk.size());
      w = "" + c;
    }
  }
  if (!w.isEmpty())
    kod.add(sozluk.get(w));
  return kod;
}

LZW

LZW Açma (Decompression)

  • Açıcı yalnızca başlangıç sözlüğünü bilir; sözlüğü sıkıştırıcıyla aynı sırada kendisi kurar.
  • Her yeni kod k için: e = sözlük[k] yazılır; sözlüğe w + e[0] eklenir; w = e.
  • Açıcı sözlükte bir adım geride kalır. k henüz tanımlı değilse (k = sonraki kod), e = w + w[0] olmak zorundadır (cScSc durumu).
  • Çıktı: A·B·AB·C·AB·ABC·A = ABABCABABCA ✓
KodÇıktıEklenen
0A—
1B3 = AB
3AB4 = BA
2C5 = ABC
3AB6 = CA
5ABC7 = ABA
0A8 = ABCA

LZW

LZW Örneği: DNA Dizisi

Metin: ATGATCATGAG (11 sembol)

Başlangıç: A = 0, T = 1, G = 2, C = 3

Çıktı: 0 1 2 4 3 4 6 2 (8 kod)

  • Her satır: sözlüğe eklenen yeni dizgi ve o anda çıktıya yazılan kod.
  • "AT" ikinci kez görüldüğünde tek kodla (4) yazılır; "GA" (6) da öyle.
  • Son w = "G" döngüden sonra çıktıya eklenir (2).
KodSözlüğe eklenenÇıktı
4AT0
5TG1
6GA2
7ATC4
8CA3
9ATG4
10GAG6
—(son w = G)2

LZW

ASCII Sözlüklü LZW ve Özel Durum

  • Pratikte başlangıç sözlüğü 256 ASCII karakteridir (a = 97, b = 98, c = 99); yeni dizgiler 256'dan itibaren numaralanır.
  • Metin: aabaacababacb (13 karakter) → 9 kod: 97 97 98 256 99 257 261 99 98.
  • "aba" = 261, "ab" (257) yazılırken eklenir ve hemen bir sonraki kodda kullanılır.
  • Açıcı 261'i okuduğunda sözlüğünde henüz 260'a kadar kod vardır → özel durum: e = w + w[0] = "ab" + "a" = "aba".
ÇıktıSözlüğe eklenen
97256 = aa
97257 = ab
98258 = ba
256259 = aac
99260 = ca
257261 = aba
261262 = abac
99263 = cb
98(son w = b)

LZW

LZ78: LZW'nin Atası

  • LZ78 sözlüğü boş başlar; çıktı (indeks, karakter) çiftleridir: sözlükteki en uzun eşleşmenin indeksi (yoksa 0) + onu izleyen karakter.
  • Metin: aabaacabcabcb → (0,a) (1,b) (1,a) (0,c) (2,c) (5,b).
  • Welch'in değişikliği (LZW): sözlüğü tüm tek karakterlerle başlatmak → çıktı yalnızca kodlardan oluşur, karakter yazmaya gerek kalmaz.
  • LZ77 (kayan pencere) ailesi ise gzip/ZIP (DEFLATE) ve PNG'nin temelidir.
ÇıktıSözlük
(0, a)1 = a
(1, b)2 = ab
(1, a)3 = aa
(0, c)4 = c
(2, c)5 = abc
(5, b)6 = abcb

LZW

RLE ve LZW Karşılaştırması

RLELZW
Yakaladığı fazlalıkaynı sembolün ardışık tekrarıtekrar eden dizgiler (ardışık olmasa da)
Ek yapıyoksözlük (hash tablosu / trie)
ZamanO(n)O(n) (sözlük erişimi O(1) ortalama)
En kötü durumtekrar yoksa çıktı 2 katına çıkarkısa/rastgele veride kazanç az, kod genişliği büyür
Kullanımfaks, BMP/PCX, basit grafiklerGIF, TIFF, PDF, Unix compress
Her ikisi de kayıpsızdır: sıkıştırılmış veri açıldığında orijinal veri aynen elde edilir. Hiçbir kayıpsız yöntem her girdiyi küçültemez (güvercin yuvası ilkesi).

Özet

Özet

KonuHatırlanacaklar
Kaba kuvvether kaymayı soldan sağa dene; ön işleme yok — en kötü O(n · m)
KMPlps[q] = en uzun uygun önek = sonek; uyuşmazlıkta q = lps[q − 1], i geri gitmez — O(n + m)
Boyer-Mooresağdan sola; s += max(1, j − son[c]) ve iyi sonek; en iyi O(n/m), pratikte en hızlı
Rabin-Karppolinom kayan hash, O(1) güncelleme; hash eşitse doğrula; beklenen O(n + m)
RLEkoşu → (sayı, sembol); O(n); tekrar yoksa büyütür; bayrak baytı ile iyileştirilir
LZWw + c sözlükte varsa uzat, yoksa kod(w) yaz ve w + c'yi ekle; açıcı sözlüğü kendisi kurar (özel durum: w + w[0])

Özet

Etkileşimli Simülatörler

Dizgi EşleştirmeKendi metninizi ve örüntünüzü girin; Naive, KMP, Boyer-Moore (kötü karakter tablosu) ve Rabin-Karp (taban 256, mod 101 hash) algoritmalarını adım adım ya da otomatik oynatarak izleyin, bulunan eşleşmeleri görün.
Dizgi SıkıştırmaRLE ve LZW'yi bir girdi üzerinde adım adım çalıştırın; LZW sözlük tablosunun büyümesini, sıkıştırılmış çıktıyı ve sıkıştırma oranını izleyin.
Önek Fonksiyonu ve Z AlgoritmasıBir dizgi için KMP önek (π, LPS) dizisini ve Z dizisini kurup yan yana karşılaştırın.

Özet

Sık Yapılan Hatalar

Döngü sınırıKaba kuvvette i < n − m yazmak son kaymayı (s = n − m) atlar; doğrusu i <= n − m.
LPS'yi yanlış tanımlamakÖrüntünün kendisi sayılmaz: lps("aaaa") = [0, 1, 2, 3], [1, 2, 3, 4] değil. KMP'de uyuşmazlıkta lps[q − 1] kullanılır, lps[q] değil.
Negatif kaydırmaBoyer-Moore'da j − son[c] negatif olabilir (c, j'nin sağında). max(1, …) unutulursa örüntü geri gider ya da sonsuz döngü olur.
Hash eşitliğine güvenmekRabin-Karp'ta hash eşitliği eşleşme demek değildir: doğrulama yapılmalı. Java'da % negatif sonuç verebilir: if (th < 0) th += Q.
RLE'de çok basamaklı sayılar"12A" çözülürken sayıyı tek rakam okumak hatalıdır (1 tane '2'?). Sayı sınırlanmalı ya da bayt olarak yazılmalı.
LZW'de son w ve özel durumDöngüden sonra kalan w'nin kodunu yazmayı unutmak; açarken henüz tanımlanmamış kodu (w + w[0]) ele almamak.

Özet

Örnek Problemler

  1. "aabaaab" örüntüsünün LPS tablosunu bulun.
  2. T = "aaaaaaaa" (n = 8), P = "aab" için kaba kuvvet kaç karşılaştırma yapar?
  3. Boyer-Moore (yalnız kötü karakter): T = "GCAATGCCTATGTGACC", P = "TATGTG". İlk kaydırma kaç olur?
  4. Taban 10, Q = 13, karakterler rakam: T = "31415", m = 3. h("314") verildiğinde h("141")'i kaydırma formülüyle bulun.
  5. "WWWWBBBWWWWWWB" dizgisini RLE ile kodlayın; oranı hesaplayın.
  6. A = 0, B = 1 sözlüğüyle "ABABABA" dizgisini LZW ile sıkıştırın.
Cevapları göster
  1. [0, 1, 0, 1, 2, 2, 3]
  2. 6 hiza × 3 = 18 (her hizada "aa" eşleşir, 'b' uyuşmaz); eşleşme yok.
  3. s = 0: p[5] = t[5] = 'G' ve p[4] = t[4] = 'T' eşleşir; p[3] = 'G' ≠ t[3] = 'A' → son['A'] = 1 → 3 − 1 = 2.
  4. h("314") = 314 mod 13 = 2; h' = (10 · (2 − 3 · 9) + 1) mod 13 = (−249) mod 13 = 11 = 141 mod 13 ✓ (10² mod 13 = 9).
  5. 4W3B6W1B → 8 karakter / 14 ≈ 0,57.
  6. AB = 2, BA = 3, ABA = 4 eklenir; çıktı 0 1 2 4 (açarken 4 özel durumdur: "AB" + "A").

Özet

Alıştırmalar

  1. KMP aramasını, tüm geçişleri bir listede döndürecek şekilde değiştirin.
  2. "abcbcabbabcabc" deseninin son görülme tablosunu ve "abccbaabccbaabcbcabbabcabc" metnindeki ilk kaydırmayı bulun.
  3. Rabin-Karp ile aynı uzunlukta k örüntüyü tek geçişte arayan bir yöntem tasarlayın.
  4. rleAc(String s): çok basamaklı sayıları da doğru çözen RLE açıcıyı yazın.
  5. 0 1 3 2 3 5 0 kodlarını (A = 0, B = 1, C = 2) LZW ile açın.
  6. Kaba kuvvetin en kötü durumunu üreten bir T, P çifti (n = 12, m = 4) verin; karşılaştırma sayısını yazın.
Cevaplar / ipuçları
  1. q == m olduğunda liste.add(i − m + 1); q = lps[q − 1]; ile devam edin.
  2. a: 11, b: 12, c: 13. s = 0: p[13] = 'c' ≠ t[13] = 'b' → 13 − 12 = 1.
  3. Örüntü hash'lerini bir HashSet'e koyun; her pencere hash'ini kümede arayın, bulunursa doğrulayın: beklenen O(n + k · m).
  4. Rakamları Character.isDigit ile biriktirip sayi = sayi · 10 + rakam; harf gelince sayi kadar yazın.
  5. A, B, AB, C, AB, ABC, A → ABABCABABCA.
  6. T = "aaaaaaaaaaaa", P = "aaab": 9 hiza × 4 = 36 karşılaştırma.

Özet

Kaynaklar

  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022 (Bölüm 32: String Matching).
  • Sedgewick ve Wayne, Algorithms, 4. baskı, 2011 (Bölüm 5.3 Substring Search, 5.5 Data Compression).
  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014 (Bölüm 13).
  • D. E. Knuth, J. H. Morris, V. R. Pratt, "Fast Pattern Matching in Strings", SIAM J. Computing 6(2), 1977.
  • R. S. Boyer, J S. Moore, "A Fast String Searching Algorithm", Communications of the ACM 20(10), 1977.
  • R. M. Karp, M. O. Rabin, "Efficient Randomized Pattern-Matching Algorithms", IBM J. Res. Dev. 31(2), 1987.
  • T. A. Welch, "A Technique for High-Performance Data Compression", IEEE Computer 17(6), 1984.

Son

Bölüm 5: Dizgi Algoritmaları
Sonraki bölüm: Bölüm 5: Dizgi Algoritmaları (2)
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.