[인터뷰] 문자열 알고리즘

한 구체

요구 사항 : 화이트 보드 코드 작성은 일반적으로 필요하지 않습니다.

1. 폭력적인 솔루션 ( O (MN) O (MN)O ( M N )

// Java
public static int forceSearch(String txt, String pat) {
    
    
    int M = txt.length();
    int N = pat.length();

    for (int i = 0; i <= M - N; i++) {
    
    
        int j;
        for (j = 0; j < N; j++) {
    
    
            if (txt.charAt(i + j) != pat.charAt(j))
                break;
        }
        if (j == N) {
    
    
            return i;
        }
        // 更加聪明? 
        // 1. 预先判断 hash(txt.substring(i, M)) == hash(pat)
        // 2. KMP 
    }
    return -1;
}

2. Rabin-Karp 알고리즘

// Java
public final static int D = 256;
public final static int Q = 9997;

static int RabinKarpSerach(String txt, String pat) {
    
    
    int M = pat.length();
    int N = txt.length();
    int i, j;
    int patHash = 0, txtHash = 0;

    for (i = 0; i < M; i++) {
    
    
        patHash = (D * patHash + pat.charAt(i)) % Q;
        txtHash = (D * txtHash + txt.charAt(i)) % Q;
    }
    int highestPow = 1;  // pow(256, M-1)
    for (i = 0; i < M - 1; i++) 
        highestPow = (highestPow * D) % Q;

    for (i = 0; i <= N - M; i++) {
    
     // 枚举起点
        if (patHash == txtHash) {
    
    
            for (j = 0; j < M; j++) {
    
    
                if (txt.charAt(i + j) != pat.charAt(j))
                    break;
            }
            if (j == M)
                return i;
        }
        if (i < N - M) {
    
    
            txtHash = (D * (txtHash - txt.charAt(i) * highestPow) + txt.charAt(i + M)) % Q;
            if (txtHash < 0)
                txtHash += Q;
        }
    }

    return -1;
}

3KMP

자세한 내용은 1, 2, 3의 동영상 및 기사를 참조하십시오.

3.1, 핵심 개념

  • 접두사 : 마지막 문자를 제외하고 문자열의 모든 머리 조합;

  • 접미사 : 첫 번째 문자를 제외하고 문자열의 모든 꼬리 조합.

    예 : 문자열 S = "ABCDAB", 접두사는 [A, AB, ABC, ABCD, ABCDA], 접미사는 [BCDAB, CDAB, DAB, AB, B], 총 요소는 "AB", 길이는 2입니다.

3.2, KMP 처리 프로세스

1 : 접두사 표

문자열의 경우 모든 문자열의 접두사와 접미사 중 가장 긴 공통 요소의 길이를 찾습니다.
접두사 표

계산 된 공통 길이를 한 위치 뒤로 이동하면 첫 번째 위치는 -1입니다.
프로세스 1
2. 문자열 매칭

1 단계 : i = 0, j = 0에서 시작하여 하나씩 같은지 비교합니다.
프로세스 1.1
2 단계 : 부등식이 발생하면 먼저 접두사 테이블 아래의 해당 번호를 확인한 다음 포인터를 패턴 문자열의 해당 아래 첨자로 이동합니다. 등가 효과 패턴 문자열은 전체적으로 뒤로 이동합니다.
방법
Step3 : 이동 후 효과는 아래와 같습니다.
결과

암호:

// C V2.0
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void prefix_table(char pattern[], int prefix[], int n) {
    
    
	prefix[0] = 0;
	int len = 0;
	int i = 1;
	while (i<n) {
    
    
		if ( pattern[i]==pattern[len] ) {
    
      // ABA --> ABAB, 最长公共前缀长度由1 --> 2
			len++;
			prefix[i] = len;
			i++;
		}
		else {
    
    
			if (len > 0) {
    
      // 防止越界,
				// ABABCABA --> ABABCABAA。关键步骤,补充理解见下。
				len = prefix[len-1];
			}
			else {
    
      // pattern[i]!=pattern[len] && len==0, 防止死循环 A --> AB, 0 --> -1, 此时i = 0
				prefix[i] = len;
				i++;
			}
		}
	}
}

void move_prefix_table(int prefix[], int n) {
    
    
	int i;
	for (i=n-1; i>0; i--) {
    
    
		prefix[i] = prefix[i-1];
	}
	prefix[0] = -1;
}

void kmp_search(char text[], char pattern[]) {
    
    
	int n = strlen(pattern);
	int m = strlen(text);
	int* prefix = malloc(sizeof(int) * n);
	prefix_table(pattern, prefix, n);
	move_prefix_table(prefix, n);
	
	// text[i]    , len(text)    = m 
	// pattern[i] , len(pattern) = n 
	
	int i = 0;
	int j = 0;
	while (i < m) {
    
    
		if (j==n-1 && text[i] == pattern[j]) {
    
    
			printf("Found pattern at %d\n", i-j);
			j = prefix[j];
		}
		if (text[i]==pattern[j]) {
    
    
			i++; j++;
		}
		else {
    
    
			j = prefix[j];  // 关键过程可见上图
			if (j == -1) {
    
    
				i++; j++;
			}
		}
	}
}
/**/
int main() {
    
    
	/**/
	char pattern[] = "ABABCABAA";
	char text[]    = "ABABABCABAABABABAB";
	kmp_search(text, pattern);

	/*
	char pattern[] = "ABABCABAA";
	int prefix[9];
	int n = 9;
	prefix_table(pattern, prefix, n);
	move_prefix_table(prefix, n);
	int i;
	for (i=0; i<n; i++) {
		printf("%d\n", prefix[i]);
	}
    */
	
	return 0;
}

시간 복잡도 : O (m + n), {aaaaaaaab, ab}와 같이 최악의 경우 O (mn)로 저하 될 수
있습니다. 공간 복잡도 : O (1)

  • 보충 이해 : 다음 어레이를 빠르게 구축하는 방법

요점 : 패턴 문자열은 자체적으로 일치합니다.
next [i]의 정의 : P [0] ~ P [i] 문자열의이 섹션에서 접두사와 접미사의 최대 공통 문자열의 길이, 즉 k 접미사와 동일한 k 접두사의 최대 k.

이해해야 할 주요 단계 : len = prefix [len-1], 왜? (분석은 참조 7에서 제공됨)

목표 : 이제 index = 12, 다음 배열의 마지막 숫자의 k 값, 즉 next [last] =? 다음을 찾는 방법 [i + 1]

  • 알려진 정보 :
  1. 이제 문자열 A는 문자열 B와 동일합니다. next [last-1] = String_A.length = String_B.length = 5입니다.
  2. 단락의 전반부에서 next [now-1] = {a, b} .length = 2, 2는 하위 문자열 A의 접두사와 접미사의 가장 큰 공통 문자열의 길이입니다.
  • 분석:
  1. P [now] == P [x]이면 바로 앞 자리에 +1을 더하면 5 + 1 = 6이됩니다.
  2. 하지만 P [지금]! = P [x], 다음 [마지막] = 다음 [지금] = 0? 당연히 아닙니다.
  • Find next [last] =? P [0] ~ P [x-1]의 공통 접두사와 접미사를 알아야합니다.

String A = string B, P [now]! = P [x], 즉 접두어의 가장 큰 공통 문자를 이미 알고 있고 접미사 문자열 A 또는 B가 충족되지 않습니다. 공통 문자열을 줄여야하며 접두사와 접미사를 더 줄여야합니다.

분석을 계속하면 다음 반복의 접두사 (축약 됨)는 문자열 A에 속해야하고 접미사는 문자열 B에 속해야합니다. String A == String B이므로 문자열 A의 접두사와 접미사의 최대 공통 길이, 즉 next [now-1]을 찾습니다.

이제 점프, 특정 값 : now = next [now-1], 다음 비교 p [now] == p [x]? 같으면 다음 [마지막] = 지금 +1, 그렇지 않으면 지금 = 다음 [지금 -1], 루프를 계속합니다.
결과

둘째, 참조

1. KMP 문자열 매칭 알고리즘 1
2. KMP 문자열 매칭 알고리즘 2
3. 문자열 매칭을 위한 KMP 알고리즘
4, 문자열 매칭을 위한 KMP , BoyerMoore, 일요일 알고리즘
5, 문자열 매칭 무차별 대입 코드 예제
6, Rabin-Karp 코드 샘플
7. KMP 알고리즘을 더 잘 이해하고 마스터하는 방법은 무엇입니까?

추천

출처blog.csdn.net/HeavenDan/article/details/109165811