[Algorithm] KMP algorithm

String match algorithm인 KMP algorithm은 text에서 뛰어넘을 수 있는 만큼 뛰어넘으면서 string이 match되는지 확인한다. 여기서 얼마나 뛰어넘을 수 있는지에 대한 정보를 얻기 위해 Failure function이라는 함수를 이용한다.

Failure function은 찾으려는 string의 각 위치마다 공통인 prefix와 suffix의 최대 길이를 찾아 저장해둔다. 예를 들면, “ababa” 는  { 0, 0, 1, 2, 3 } 과 같이 저장한다. 구현 시에는 -1 해서 저장한다. 예에서의 값은 { -1, -1, 0, 1, 2 } 이 된다.

void get_failure_function(char str[], int len, int failure[])
{
	int i;

	failure[0] = -1;

	for (i = 1; i < len; i++) {
		int f = failure[i - 1];

		while (1) {
			if (str[i] == str[f + 1]) {
				failure[i] = f + 1;
				break;
			}
			if (f <= -1) {
				failure[i] = -1;
				break;
			}
			f = failure[f];
		}
	}
}

 

찾을 때는, 위에서 계산한 값을 이용해 string을 찾는 도중에 틀리면 바로 이전까지 맞았던 부분의 failure function value를 찾아 이전까지 같은 부분을 건너뛰고 다음 부분부터 비교한다. 예를 들어, 위의 string을 찾으려고 할 때, text가 “ababcababa” 라면, string의 처음부터 비교하다가 5번째에서 틀리면, 4번째까지는 맞으므로, 4번째의 failure function value를 찾으면 2이다. 이는 앞의 2글자는 맞다는 말이므로, text에서 4-2 만큼을 건너뛴 후, string의 3번째부터 다시 비교한다. string의 3번째도 틀리므로, 2번째의 failure function value를 찾으면 0인데, 이는 0만큼 맞다는 말(=하나도 안맞다는 말)이므로 text를 2-0 만큼 건너뛴 후, string의 1번째와 다시 비교한다. string의 첫번째와 다르면  1만큼만 이동하고, 다시 1번째와 비교한다. 맞았을 때도 맞은 부분만큼을 이용해서 건너뛴다(5번째에서 맞았으므로, 3글자가 맞고, 5-3만큼 이동한 후, 4번째부터 비교).

아래 구현은 match되는 개수를 return하도록 하였다.

int get_matched_count(char text[], int text_len, char string[], int str_len, int failure[])
{
	int count = 0;
	int t = 0;
	int s = 0;

	while (t < text_len - str_len + 1) {
		while (s < str_len) {
			if (text[t + s] != string[s])
				break;
			s++;
		}

		if (s == str_len) {
			// MATCH!
			count++;
		}

		if (s < 1) {
			t++;
			s = 0;
		} else {
			t += s - 1 - failure[s - 1];
			s = failure[s - 1] + 1;
		}
	}

	return count;
}

 

앞 부분과 공통 부분이 없는 string인 경우, 더 빨리 건너뛴다. 예를 들면, “abababababc” 에서 “ababc”({ 0, 0, 1, 2, 0 })를 찾는 경우, 5번째에서 틀리면, 앞에서부터 0글자가 같으므로, 4-0만큼 이동한 후, 1번째부터 다시 비교한다.

위 두 구현 함수의 사용 부분은 다음과 같이 될 것이다.

		int T, S;
		int count;
		int *failure_function = NULL;

		char *text = "ababcababababababababa";
		char *string = "ababa";

		T = strlen(text);
		S = strlen(string);

		failure_function = (int *)malloc(sizeof(int) * S);
		// skip the check of the memory allocation

		get_failure_function(string, S, failure_function);

		count = get_matched_count(text, T, string, S, failure_function);

		printf("%d matched\n", count);