[Algorithm] Strings - Rabin Karp Algorithm

문자열 라빈 카프

Posted by lim.Chuck on September 27, 2024

Rabin-Karp?

Rabin-Karp 알고리즘은 문자열에서 특정 패턴을 찾는 데 사용되는 효율적인 해시 기반 문자열 검색 알고리즘입니다. 이 알고리즘의 핵심은 해시 함수를 이용해 텍스트와 패턴의 해시 값을 계산하고, 이를 비교하여 패턴을 찾는 방식입니다.

동작 방식:

  1. 패턴과 텍스트의 첫 번째 부분의 해시 값을 계산합니다.
  2. 이후 텍스트의 다음 부분을 한 글자씩 이동하면서 해시 값을 업데이트하고, 패턴의 해시 값과 비교합니다.
  3. 해시 값이 일치하는 경우, 문자열을 실제로 비교하여 패턴이 맞는지 확인합니다.
  4. 해시 충돌이 발생할 수 있으므로, 해시 값이 일치할 때 실제로 문자열을 비교하는 단계가 필요합니다.

사용 사례:

  • 문자열 검색: 긴 텍스트에서 짧은 패턴을 효율적으로 검색하는 데 사용됩니다.
  • Plagiarism Detection(표절 검사): 해시 값을 사용해 두 문서가 비슷한지 빠르게 확인할 수 있습니다.
  • DNA 서열 분석: 유전자 서열에서 특정 패턴을 찾는 데 사용될 수 있습니다.

JavaScript 예제 코드: Rabin-Karp 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function rabinKarp(text, pattern) {
  const d = 256; // 알파벳 수
  const q = 101; // 소수 (해시 충돌을 줄이기 위해 사용)
  const m = pattern.length;
  const n = text.length;
  let p = 0; // 패턴의 해시 값
  let t = 0; // 텍스트의 해시 값
  let h = 1;

  // h의 값을 계산, h = pow(d, m-1) % q
  for (let i = 0; i < m - 1; i++) {
    h = (h * d) % q;
  }

  // 패턴과 텍스트 첫 번째 윈도우의 해시 값을 계산
  for (let i = 0; i < m; i++) {
    p = (d * p + pattern.charCodeAt(i)) % q;
    t = (d * t + text.charCodeAt(i)) % q;
  }

  // 텍스트에서 패턴을 검색
  for (let i = 0; i <= n - m; i++) {
    // 해시 값이 같으면 실제 문자열을 비교
    if (p === t) {
      let j = 0;
      for (j = 0; j < m; j++) {
        if (text[i + j] !== pattern[j]) {
          break;
        }
      }
      if (j === m) {
        console.log(`Pattern found at index ${i}`);
      }
    }

    // 다음 윈도우의 해시 값을 계산
    if (i < n - m) {
      t = (d * (t - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % q;
      if (t < 0) {
        t = t + q;
      }
    }
  }
}

// 사용 예시
const text = "GEEKS FOR GEEKS";
const pattern = "GEEK";
rabinKarp(text, pattern); // Output: Pattern found at index 0, 10

설명:

  • rabinKarp 함수는 텍스트에서 패턴을 검색하며, 각 윈도우의 해시 값을 계산하고 비교하여 일치하는 패턴을 찾습니다.
  • 해시 충돌이 발생할 수 있으므로 해시 값이 동일할 때는 실제 문자열 비교를 수행합니다.

Rust 예제 코드: Rabin-Karp 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
fn rabin_karp(text: &str, pattern: &str) {
    let d: i32 = 256;
    let q: i32 = 101;
    let m = pattern.len();
    let n = text.len();
    let mut p: i32 = 0; // 패턴의 해시 값
    let mut t: i32 = 0; // 텍스트의 해시 값
    let mut h: i32 = 1;

    // h = pow(d, m-1) % q 계산
    for _ in 0..(m-1) {
        h = (h * d) % q;
    }

    // 패턴과 텍스트의 첫 윈도우 해시 값 계산
    for i in 0..m {
        p = (d * p + pattern.as_bytes()[i] as i32) % q;
        t = (d * t + text.as_bytes()[i] as i32) % q;
    }

    // 텍스트에서 패턴을 검색
    for i in 0..=(n - m) {
        // 해시 값이 일치하면 실제로 문자열을 비교
        if p == t {
            if &text[i..i+m] == pattern {
                println!("Pattern found at index {}", i);
            }
        }

        // 다음 윈도우의 해시 값을 계산
        if i < n - m {
            t = (d * (t - (text.as_bytes()[i] as i32 * h)) + text.as_bytes()[i + m] as i32) % q;
            if t < 0 {
                t = t + q;
            }
        }
    }
}

fn main() {
    let text = "GEEKS FOR GEEKS";
    let pattern = "GEEK";
    rabin_karp(text, pattern); // Output: Pattern found at index 0, 10
}

설명:

  • Rust 코드는 JavaScript 코드와 동일한 논리를 따르며, 텍스트와 패턴의 해시 값을 계산하여 패턴을 검색합니다.
  • 해시 값이 동일할 때는 실제로 패턴과 텍스트를 비교하여 패턴이 일치하는지 확인합니다.

요약:

Rabin-Karp 알고리즘은 해시 함수를 사용하여 텍스트 내에서 특정 패턴을 찾는 효율적인 방법입니다. 특히, 여러 패턴을 검색하거나 매우 긴 텍스트에서 짧은 패턴을 찾는 경우 유용하게 사용됩니다.

Rabin-Karp Algorithm?

The Rabin-Karp algorithm is an efficient, hash-based string searching algorithm used to find specific patterns within a string. The key idea behind the algorithm is to calculate and compare the hash values of the text and the pattern to efficiently locate the desired pattern.

How it works:

  1. Compute the hash values of the pattern and the first part of the text.
  2. Slide over the text one character at a time, updating the hash value for the next part of the text and comparing it with the pattern’s hash.
  3. If the hash values match, perform an actual string comparison to confirm the pattern.
  4. Handle potential hash collisions by verifying the actual characters when a hash match is found.

Use Cases:

  • String Searching: Efficiently find short patterns in large texts.
  • Plagiarism Detection: Quickly compare documents to check for similarities by hashing content.
  • DNA Sequence Analysis: Used to identify specific patterns in gene sequences.

JavaScript Example: Rabin-Karp Algorithm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function rabinKarp(text, pattern) {
  const d = 256; // Number of characters in the input alphabet
  const q = 101; // A prime number to reduce hash collisions
  const m = pattern.length;
  const n = text.length;
  let p = 0; // Hash value for the pattern
  let t = 0; // Hash value for text
  let h = 1;

  // Calculate the value of h = pow(d, m-1) % q
  for (let i = 0; i < m - 1; i++) {
    h = (h * d) % q;
  }

  // Calculate the initial hash values for the pattern and the first window of the text
  for (let i = 0; i < m; i++) {
    p = (d * p + pattern.charCodeAt(i)) % q;
    t = (d * t + text.charCodeAt(i)) % q;
  }

  // Slide the pattern over the text
  for (let i = 0; i <= n - m; i++) {
    // If hash values match, check the actual characters
    if (p === t) {
      let j = 0;
      for (j = 0; j < m; j++) {
        if (text[i + j] !== pattern[j]) {
          break;
        }
      }
      if (j === m) {
        console.log(`Pattern found at index ${i}`);
      }
    }

    // Calculate hash for next window
    if (i < n - m) {
      t = (d * (t - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % q;
      if (t < 0) {
        t = t + q;
      }
    }
  }
}

// Usage example
const text = "GEEKS FOR GEEKS";
const pattern = "GEEK";
rabinKarp(text, pattern); // Output: Pattern found at index 0, 10

Explanation:

  • The rabinKarp function scans the text and searches for the pattern by computing hash values and comparing them.
  • If hash values match, an actual string comparison is done to verify that the pattern exists at that position.

Rust Example: Rabin-Karp Algorithm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
fn rabin_karp(text: &str, pattern: &str) {
    let d: i32 = 256;
    let q: i32 = 101;
    let m = pattern.len();
    let n = text.len();
    let mut p: i32 = 0; // Hash value for pattern
    let mut t: i32 = 0; // Hash value for text
    let mut h: i32 = 1;

    // Calculate h = pow(d, m-1) % q
    for _ in 0..(m-1) {
        h = (h * d) % q;
    }

    // Calculate the initial hash values for the pattern and the first window of the text
    for i in 0..m {
        p = (d * p + pattern.as_bytes()[i] as i32) % q;
        t = (d * t + text.as_bytes()[i] as i32) % q;
    }

    // Slide the pattern over the text
    for i in 0..=(n - m) {
        // If hash values match, compare the actual strings
        if p == t {
            if &text[i..i+m] == pattern {
                println!("Pattern found at index {}", i);
            }
        }

        // Calculate the hash value for the next window
        if i < n - m {
            t = (d * (t - (text.as_bytes()[i] as i32 * h)) + text.as_bytes()[i + m] as i32) % q;
            if t < 0 {
                t = t + q;
            }
        }
    }
}

fn main() {
    let text = "GEEKS FOR GEEKS";
    let pattern = "GEEK";
    rabin_karp(text, pattern); // Output: Pattern found at index 0, 10
}

Explanation:

  • Rust code follows the same logic as the JavaScript code, computing the hash values of both the pattern and the text.
  • When a hash match occurs, it performs a direct string comparison to confirm the pattern match.

Summary:

The Rabin-Karp algorithm is an efficient method for searching for a pattern in a text using hash functions. It is particularly useful in scenarios where you need to search for multiple patterns or detect patterns in large bodies of text.