Algoritma Cari Rentetan untuk Teks Besar dengan Java

1. Pengenalan

Dalam artikel ini, kami akan menunjukkan beberapa algoritma untuk mencari corak dalam teks besar. Kami akan menerangkan setiap algoritma dengan kod yang disediakan dan latar belakang matematik yang mudah.

Perhatikan bahawa algoritma yang disediakan bukanlah cara terbaik untuk melakukan carian teks penuh dalam aplikasi yang lebih kompleks. Untuk melakukan carian teks penuh dengan betul, kita dapat menggunakan Solr atau ElasticSearch.

2. Algoritma

Kita akan mulakan dengan algoritma carian teks naif yang paling intuitif dan membantu menemui masalah lanjutan lain yang berkaitan dengan tugas itu.

2.1. Kaedah Pembantu

Sebelum memulakan, mari tentukan kaedah mudah untuk mengira nombor perdana yang kami gunakan dalam algoritma Rabin Karp:

public static long getBiggerPrime(int m) { BigInteger prime = BigInteger.probablePrime(getNumberOfBits(m) + 1, new Random()); return prime.longValue(); } private static int getNumberOfBits(int number) { return Integer.SIZE - Integer.numberOfLeadingZeros(number); } 

2.2. Pencarian Teks Ringkas

Nama algoritma ini menerangkannya lebih baik daripada penjelasan lain. Ini adalah penyelesaian yang paling semula jadi:

public static int simpleTextSearch(char[] pattern, char[] text) { int patternSize = pattern.length; int textSize = text.length; int i = 0; while ((i + patternSize) = patternSize) return i; } i += 1; } return -1; }

Idea algoritma ini mudah: berulang melalui teks dan jika terdapat padanan untuk huruf pertama corak, periksa apakah semua huruf pola sesuai dengan teks.

Sekiranya m adalah sebilangan huruf dalam corak, dan n adalah bilangan huruf dalam teks, kerumitan waktu algoritma ini adalah O (m (nm + 1)) .

Senario terburuk berlaku dalam kes String yang mempunyai banyak kejadian separa:

Text: baeldunbaeldunbaeldunbaeldun Pattern: baeldung

2.3. Algoritma Rabin Karp

Seperti yang dinyatakan di atas, algoritma Pencarian Teks Sederhana sangat tidak cekap ketika corak panjang dan ketika terdapat banyak unsur corak yang berulang.

Idea algoritma Rabin Karp adalah menggunakan hashing untuk mencari corak dalam teks. Pada permulaan algoritma, kita perlu mengira hash corak yang kemudian digunakan dalam algoritma. Proses ini disebut pengiraan cap jari, dan kita dapat mencari penjelasan terperinci di sini.

Perkara penting mengenai langkah pra-pemprosesan adalah bahawa kerumitan waktunya adalah O (m) dan lelaran melalui teks akan mengambil O (n) yang memberikan kerumitan masa seluruh algoritma O (m + n) .

Kod algoritma:

public static int RabinKarpMethod(char[] pattern, char[] text) { int patternSize = pattern.length; int textSize = text.length; long prime = getBiggerPrime(patternSize); long r = 1; for (int i = 0; i < patternSize - 1; i++) { r *= 2; r = r % prime; } long[] t = new long[textSize]; t[0] = 0; long pfinger = 0; for (int j = 0; j < patternSize; j++) { t[0] = (2 * t[0] + text[j]) % prime; pfinger = (2 * pfinger + pattern[j]) % prime; } int i = 0; boolean passed = false; int diff = textSize - patternSize; for (i = 0; i <= diff; i++) { if (t[i] == pfinger) { passed = true; for (int k = 0; k < patternSize; k++) { if (text[i + k] != pattern[k]) { passed = false; break; } } if (passed) { return i; } } if (i < diff) { long value = 2 * (t[i] - r * text[i]) + text[i + patternSize]; t[i + 1] = ((value % prime) + prime) % prime; } } return -1; }

Dalam senario terburuk, kerumitan masa untuk algoritma ini adalah O (m (n-m + 1)) . Walau bagaimanapun, secara purata algoritma ini mempunyai kerumitan masa O (n + m) .

Selain itu, ada versi Monte Carlo dari algoritma ini yang lebih pantas, tetapi boleh mengakibatkan padanan yang salah (positif palsu).

2.4 Algoritma Knuth-Morris-Pratt

Dalam algoritma Pencarian Teks Sederhana, kami melihat bagaimana algoritma boleh menjadi perlahan sekiranya terdapat banyak bahagian teks yang sesuai dengan corak.

Idea algoritma Knuth-Morris-Pratt adalah pengiraan jadual shift yang memberi kita maklumat di mana kita harus mencari calon corak kita.

Pelaksanaan algoritma KMP Java:

public static int KnuthMorrisPrattSearch(char[] pattern, char[] text) { int patternSize = pattern.length; int textSize = text.length; int i = 0, j = 0; int[] shift = KnuthMorrisPrattShift(pattern); while ((i + patternSize) = patternSize) return i; } if (j > 0) { i += shift[j - 1]; j = Math.max(j - shift[j - 1], 0); } else { i++; j = 0; } } return -1; }

Dan inilah cara kami mengira jadual shift:

public static int[] KnuthMorrisPrattShift(char[] pattern) { int patternSize = pattern.length; int[] shift = new int[patternSize]; shift[0] = 1; int i = 1, j = 0; while ((i + j) 
    
      0) { i = i + shift[j - 1]; j = Math.max(j - shift[j - 1], 0); } else { i = i + 1; j = 0; } } } return shift; }
    

Kerumitan masa algoritma ini juga O (m + n) .

2.5. Algoritma Boyer-Moore Sederhana

Dua saintis, Boyer dan Moore, menghasilkan idea lain. Mengapa tidak membandingkan corak dengan teks dari kanan ke kiri dan bukannya kiri ke kanan, sambil menjaga arah pergeseran tetap sama:

public static int BoyerMooreHorspoolSimpleSearch(char[] pattern, char[] text) { int patternSize = pattern.length; int textSize = text.length; int i = 0, j = 0; while ((i + patternSize) <= textSize) { j = patternSize - 1; while (text[i + j] == pattern[j]) { j--; if (j < 0) return i; } i++; } return -1; }

Seperti yang dijangkakan, ini akan berjalan dalam masa O (m * n) . Tetapi algoritma ini menyebabkan pelaksanaan kejadian dan heuristik padanan yang mempercepat algoritma dengan ketara. Kita boleh dapatkan lebih banyak lagi di sini.

2.6. Algoritma Boyer-Moore-Horspool

Terdapat banyak variasi pelaksanaan heuristik algoritma Boyer-Moore, dan yang paling mudah adalah variasi Horspool.

Versi algoritma ini disebut Boyer-Moore-Horspool, dan variasi ini menyelesaikan masalah pergeseran negatif (kita dapat membaca tentang masalah pergeseran negatif dalam keterangan algoritma Boyer-Moore).

Seperti algoritma Boyer-Moore, kerumitan masa senario terburuk adalah O (m * n) sementara kerumitan rata-rata adalah O (n). Penggunaan ruang tidak bergantung pada ukuran corak, tetapi hanya pada ukuran abjad 256 kerana itu adalah nilai maksimum karakter ASCII dalam abjad Inggeris:

public static int BoyerMooreHorspoolSearch(char[] pattern, char[] text) { int shift[] = new int[256]; for (int k = 0; k < 256; k++) { shift[k] = pattern.length; } for (int k = 0; k < pattern.length - 1; k++){ shift[pattern[k]] = pattern.length - 1 - k; } int i = 0, j = 0; while ((i + pattern.length) <= text.length) { j = pattern.length - 1; while (text[i + j] == pattern[j]) { j -= 1; if (j < 0) return i; } i = i + shift[text[i + pattern.length - 1]]; } return -1; }

4. Kesimpulan

Dalam artikel ini, kami membentangkan beberapa algoritma untuk carian teks. Oleh kerana beberapa algoritma memerlukan latar belakang matematik yang lebih kuat, kami cuba mewakili idea utama di bawah setiap algoritma dan memberikannya dengan cara yang mudah.

Dan, seperti biasa, kod sumber boleh didapati di GitHub.