KMP 알고리즘

 

  • 패턴 매칭 알고리즘의 하나로, 주어진 문자열에서 패턴과 일치하는 부분 문자열이 있는지 찾는 알고리즘

  • 일반적인 반복문을 통한 해결방법은 O(N^M)의 시간복잡도를 가지지만, 해당 알고리즘은 O(N+M)의 시간복잡도로 매우 효율적이다.

  • 패턴에서 접두사와 접미사를 이용해 중복된 검색을 줄이는 방식임

 

 

 

 

동작 원리

1. 패턴 문자열에서 0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

2. 텍스트에서 패턴을 비교하고, 검색 중 불일치가 발생하면 LPS배열을 참고해 패턴의 다음 탐색  시작 위치를 결정한다.

 

 

 

 

예시

 

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

 

1. LPS (부분 일치 테이블) 생성

0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

문자 A B C D A B D
인덱스 0 1 2 3 4 5 6
LPS 값 0 0 0 0 1 2 0

 

 

2. 문자열 검색 시작

텍스트와 패턴이 일치하지 않을 경우, LPS 테이블을 사용하여 패턴 내에서 최대한 불필요한 재비교를 피함

1) 처음 비교 시작 (i = 0, j = 0)

텍스트의 첫 번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

두 문자가 일치하므로 ij를 각각 1씩 증가시킵니다.

 

 

2) 다음 문자 비교 (i = 1, j = 1)

다음으로 텍스트의 두 번째 문자 B와 패턴의 두 번째 문자 B를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 ij를 다시 1씩 증가

 

 

3) 계속되는 비교 (i = 2, j = 2)

텍스트의 세 번째 문자 C와 패턴의 세 번째 문자 C도 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 i = 3j = 3으로 이동

 

 

4) 불일치 발생 (i = 3, j = 3)

네 번째 문자에서 텍스트의 문자 (공백)과 패턴의 문자 D가 일치하지 않음

  • 텍스트: "ABC ** **ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

불일치 시, LPS 테이블을 참고하여 패턴의 어느 위치로 돌아갈지 결정함. 현재 j = 3이고, LPS 테이블에서 LPS[3] = 0이므로, 패턴의 시작 부분으로 돌아야 함(j = 0). 하지만 텍스트에서 이미 비교한 문자는 건너뛰지 않고 계속 비교하므로 i는 4로 증가

 

5) 패턴 계속 비교 (i = 4, j = 0)

패턴을 다시 처음부터 시작하면서, 텍스트의 4번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

문자가 일치하므로 i = 5j = 1로 이동

 

6) 패턴 계속 비교 (i = 5 ~ 7, j = 1 ~ 3)

텍스트의 문자 B, C, D와 패턴의 문자 B, C, D가 차례로 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

i = 8j = 4로 이동

 

7) 다시 불일치 발생 (i = 8, j = 4)

여기서 텍스트의 문자 A와 패턴의 문자 A가 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

다시 일치하므로 i = 9j = 5로 이동

 

 

8) 최종 불일치 (i = 10, j = 6)

그러나 그다음 문자에서 텍스트의 문자 B와 패턴의 문자 B가 일치

  • 텍스트: "ABC ABCDABB ABCDABCDABDE"
  • 패턴: "ABCDABBD"

마지막으로 D가 일치

 

 

 

장점

  • 시간 복잡도를 효과적으로 줄일 수 있음

 

단점

  • 과정이 복잡해 이해하기 어려움

  • 초기 LPS 배열을 만드는 단계가 있어 패턴 크기가 작으면 오히려 비효율적일 수 있음

 

 

 

 

 

 

코드 구현

 

DP를 이용한 코드

 

>> 이전 항의 값을 별도 배열에 저장!

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class KMP알고리즘 {
	static char[] text;
	static char[] pattern;
	static int[] pi;
	static int max;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		text = br.readLine().toCharArray();
		pattern = br.readLine().toCharArray();
		
		KMP();
	}

	private static void KMP() {
		getPi();
		int len = text.length;
		
		int j = 0;
		max = 0;
		for (int i=0; i<len; i++) {
			while (j>0 && text[i] != pattern[j]) {
				if (max < j) {
					max = j;
				}
				j = pi[j-1];
			}
			
			if (text[i] == pattern[j]) {
				// j 패턴 끝까지 도달했으면 종료
				if (j == pattern.length-1) {
					System.out.println(pattern.length);
					return;
				} else {
					j++;
				}
			}
		}
		System.out.println(max);
	}
	
	// 패턴 문자배열을 0부터 i번 인덱스까지 잘랐을 때 
	// 접두사와 접미사가 같은 최대 개수를 담은 배열을 생성하는 메서드
	private static void getPi() {
		int len = pattern.length;
		pi = new int[len];
		
		int j=0;
		for (int i=1; i<len; i++) {
			// i위치와 j위치 값이 다를 때
			while (j>0 && pattern[i] != pattern[j]) {
				j=pi[j-1];
			}
			// 같을 때
			if (pattern[i] == pattern[j]) {
				pi[i] = ++j;
			}
		}
	}
}