Bölüm 5.1: Dizgi Eşleştirme ve Sıkıştırma

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, iletiler, 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.
  • İki temel konu: 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)Çalışma uzunluğu 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.

TAAAAAAs=0AABs=1AABs=2AABs=3AAB4 hiza × 3 = 12 karşılaştırma

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.
TABABABC0123456PABABCq = 4:C ≠ AABABCq = lps[3] = 2"AB" yenidendenetlenmezi = 4 yerinde kalır
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 aramaÖrüntünün ilk sembolüyle eşleşme bulunana kadar metin işaretçisi (i) ilerletilir.
Evre 2 · Birlikte ilerlemeSemboller 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 çekilmeUyuş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.
TABDCABDCABDEs=0ABDE1C yok → 4 kaydırs=4ABDE2C yok → 4 kaydırs=8ABDE3456bulundu

Kırmızı sayılar karşılaştırma sırasıdır: 12 karakterlik metinde yalnızca 6 karşılaştırma.

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: örüntü, en sağdaki c t[s + j]'nin altına gelecek şekilde kaydırılır.
  • c örüntüde hiç yoksa (son = −1): örüntü c'nin tamamen ötesine kaydırılı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, örüntü, o geçiş metindeki u'nun altına gelecek şekilde kaydırılır.
  • Durum 2: yoksa, u'nun bir sonekine eşit olan en uzun P öneki hizalanır.
  • Durum 3: hiçbiri yoksa örüntü m kaydırılı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ırma hizanın son karakterinin altındaki metin karakterine göre yapılır.
  • 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

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

horspool("LO LOELLO O HELLO", "HELLO")

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16kaydH5E5L5O5diğer5

n = 17, m = 5. kayd dizisinin 256 hücresi önce m = 5 ile dolar: örüntüde olmayan bir karakter görülürse örüntü tümüyle onun ötesine atlar.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16kaydH4E3L1O5diğer5

Son karakter hariç P[0..3] taranır: H → 4, E → 3, L önce 2 sonra 1 (en sağdaki geçiş kalır). O yalnızca sonda olduğu için 5'te kalır.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOs = 0kaydH4E3L1O5diğer5

s = 0: 2 karakter sağdan eşleşti, sonra t[2] = '␣' ≠ p[2] = 'L' → uyuşmazlık.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOHELLO→kaydH4E3L1O5diğer5

Kaydırma, uyuşmazlığın yerine değil hizanın son karakterine bakar: t[4] = 'O' → kayd = 5 → s = 5.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOs = 5kaydH4E3L1O5diğer5

s = 5: t[9] = '␣' ≠ p[4] = 'O' → uyuşmazlık.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOHELLO→kaydH4E3L1O5diğer5

Hizanın son karakteri t[9] = '␣' örüntüde yok → kayd = 5: örüntü tümüyle onun ötesine atlar, s = 10.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOs = 10kaydH4E3L1O5diğer5

s = 10: t[14] = 'L' ≠ p[4] = 'O' → uyuşmazlık.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOHELLO→kaydH4E3L1O5diğer5

Kaydırma, uyuşmazlığın yerine değil hizanın son karakterine bakar: t[14] = 'L' → kayd = 1 → s = 11.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOs = 11kaydH4E3L1O5diğer5

s = 11: t[15] = 'L' ≠ p[4] = 'O' → uyuşmazlık.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOHELLO→kaydH4E3L1O5diğer5

Kaydırma, uyuşmazlığın yerine değil hizanın son karakterine bakar: t[15] = 'L' → kayd = 1 → s = 12.

tL0O1␣2L3O4E5L6L7O8␣9O10␣11H12E13L14L15O16pHELLOs = 12kaydH4E3L1O5diğer5

Beş karakter sağdan sola eşleşti, j = −1 → s = 12 döndürülür. 5 hiza, toplam 11 karşılaştırma.

int horspool(String t, String p) {
  int n = t.length(), m = p.length();
  int[] kayd = new int[256];
  Arrays.fill(kayd, m);
  for (int i = 0; i < m - 1; i++)
    kayd[p.charAt(i)] = m - 1 - i;
  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 += kayd[t.charAt(s + m - 1)];
  }
  return -1;
}

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

Örüntü: abcbcabbabcabc (m = 14)

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

Sonuç: örüntü 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 dizgiler 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 ile 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

Çalışma Uzunluğu 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)

AAAAA5ABBB3BCCCC4CDDD3D

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 (indis) temsil eder.
  • Sözlük dinamiktir: 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

A0B1AB3C2AB3ABC5A0

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 · Kodu adım adım çalıştır

lzwAc([97 97 98 256 99 257 261 99 98])

kod970971982256399425752616997988sasözlük256'dan sonrası henüz boş

Açıcı yalnızca 256 ASCII karakterini bilir. İlk kod 97 her zaman tek karakterdir: w = "a", çıktı "a". Sözlükte henüz 256 ve sonrası yok.

kod970971982256399425752616997988saasözlük256aa

k = 97 (ASCII) → e = "a" yazılır; sözlüğe w + e[0] = "a" + "a" = 256 = "aa" eklenir.

kod970971982256399425752616997988saabsözlük256aa257ab

k = 98 (ASCII) → e = "b" yazılır; sözlüğe w + e[0] = "a" + "b" = 257 = "ab" eklenir.

kod970971982256399425752616997988saabaasözlük256aa257ab258ba

k = 256 (sözlükte 256 = "aa") → e = "aa" yazılır; sözlüğe w + e[0] = "b" + "a" = 258 = "ba" eklenir.

kod970971982256399425752616997988saabaacsözlük256aa257ab258ba259aac

k = 99 (ASCII) → e = "c" yazılır; sözlüğe w + e[0] = "aa" + "c" = 259 = "aac" eklenir.

kod970971982256399425752616997988saabaacabsözlük256aa257ab258ba259aac260ca

k = 257 (sözlükte 257 = "ab") → e = "ab" yazılır; sözlüğe w + e[0] = "c" + "a" = 260 = "ca" eklenir.

kod970971982256399425752616997988saabaacababasözlük256aa257ab258ba259aac260ca261aba

k = 261 ama sözlükte en büyük kod 260: özel durum. e = w + w[0] = "ab" + "a" = "aba"; sözlüğe de 261 = "aba" eklenir. Sıkıştırıcı bu kodu, eklediği anda kullanmıştı.

kod970971982256399425752616997988saabaacababacsözlük256aa257ab258ba259aac260ca261aba262abac

k = 99 (ASCII) → e = "c" yazılır; sözlüğe w + e[0] = "aba" + "c" = 262 = "abac" eklenir.

kod970971982256399425752616997988saabaacababacbsözlük256aa257ab258ba259aac260ca261aba262abac263cb

k = 98 (ASCII) → e = "b" yazılır; sözlüğe w + e[0] = "c" + "b" = 263 = "cb" eklenir.

kod970971982256399425752616997988saabaacababacbsözlük256aa257ab258ba259aac260ca261aba262abac263cb

9 kod, 13 karakterlik aabaacababacb metnine geri döndü; açıcının kurduğu 256–263 sözlüğü sıkıştırıcınınkiyle aynıdır.

String lzwAc(List<Integer> kod) {
  List<String> sozluk =
      ilkSozluk();      // 0..255: ASCII
  String w = sozluk.get(kod.get(0));
  StringBuilder s = new StringBuilder(w);
  for (int i = 1; i < kod.size(); i++) {
    int k = kod.get(i);
    String e = k < sozluk.size()
        ? sozluk.get(k)
        : w + w.charAt(0); // özel durum
    s.append(e);
    sozluk.add(w + e.charAt(0));
    w = e;
  }
  return s.toString();
}

LZW

LZ78: LZW'nin Atası

  • LZ78 sözlüğü boş başlar; çıktı (indis, karakter) çiftleridir: sözlükteki en uzun eşleşmenin indisi (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 · Kodu adım adım çalıştır

lz78("aabaacabcabcb")

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlükboş (indis 0 = boş dizgi)çıktı

LZ78 sözlüğü boş başlar (LZW'den farkı budur). w, sözlükte bulunan en uzun ön parçayı tutar; şimdilik boş.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1açıktı(0,a)

w boş ve 'a' sözlükte yok → (0, a) (0 = boş önek); sözlüğe 1 = "a" eklenir.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1açıktı(0,a)

w + c = "a" sözlükte var → w = "a" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2abçıktı(0,a)(1,b)

"ab" sözlükte yok → w = "a" (indis 1) ve c = 'b' yazılır: (1, b); sözlüğe 2 = "ab" eklenir, w sıfırlanır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2abçıktı(0,a)(1,b)

w + c = "a" sözlükte var → w = "a" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aaçıktı(0,a)(1,b)(1,a)

"aa" sözlükte yok → w = "a" (indis 1) ve c = 'a' yazılır: (1, a); sözlüğe 3 = "aa" eklenir, w sıfırlanır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4cçıktı(0,a)(1,b)(1,a)(0,c)

w boş ve 'c' sözlükte yok → (0, c) (0 = boş önek); sözlüğe 4 = "c" eklenir.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4cçıktı(0,a)(1,b)(1,a)(0,c)

w + c = "a" sözlükte var → w = "a" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4cçıktı(0,a)(1,b)(1,a)(0,c)

w + c = "ab" sözlükte var → w = "ab" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4c5abcçıktı(0,a)(1,b)(1,a)(0,c)(2,c)

"abc" sözlükte yok → w = "ab" (indis 2) ve c = 'c' yazılır: (2, c); sözlüğe 5 = "abc" eklenir, w sıfırlanır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4c5abcçıktı(0,a)(1,b)(1,a)(0,c)(2,c)

w + c = "a" sözlükte var → w = "a" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4c5abcçıktı(0,a)(1,b)(1,a)(0,c)(2,c)

w + c = "ab" sözlükte var → w = "ab" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4c5abcçıktı(0,a)(1,b)(1,a)(0,c)(2,c)

w + c = "abc" sözlükte var → w = "abc" olur; çıktı yazılmaz, bir sonraki karakterle daha uzun bir parça aranır.

sa0a1b2a3a4c5a6b7c8a9b10c11b12sözlük1a2ab3aa4c5abc6abcbçıktı(0,a)(1,b)(1,a)(0,c)(2,c)(5,b)

"abcb" sözlükte yok → w = "abc" (indis 5) ve c = 'b' yazılır: (5, b); sözlüğe 6 = "abcb" eklenir, w sıfırlanır. Metin bitti: 6 çift.

List<String> lz78(String s) {
  Map<String, Integer> sozluk =
      new HashMap<>();
  List<String> cikti = new ArrayList<>();
  String w = "";
  for (char c : s.toCharArray()) {
    if (sozluk.containsKey(w + c)) {
      w = w + c;               // uzat
    } else {
      int k = w.isEmpty() ? 0
                          : sozluk.get(w);
      cikti.add("(" + k + ", " + c + ")");
      sozluk.put(w + c, sozluk.size() + 1);
      w = "";
    }
  }
  if (!w.isEmpty())        // son parça
    cikti.add("(" + sozluk.get(w) + ")");
  return cikti;
}

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ştirmeHerhangi bir metin ve örüntü için Naive, KMP, Boyer-Moore (kötü karakter tablosu) ve Rabin-Karp (taban 256, mod 101 hash) adım adım ya da otomatik oynatmayla; bulunan eşleşmeler işaretlenir.
Dizgi SıkıştırmaRLE ve LZW bir girdi üzerinde adım adım: LZW sözlük tablosunun büyümesi, sıkıştırılmış çıktı ve sıkıştırma oranı.
Önek Fonksiyonu ve Z AlgoritmasıBir dizgi için KMP önek (π, LPS) dizisi ve Z dizisi yan yana.

Ö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" örüntüsünün 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.1: Dizgi Eşleştirme ve Sıkıştırma
Sonraki bölüm: Bölüm 5.2: İleri Dizgi Algoritmaları · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.