본문으로 건너뛰기
김신건의 로그

Z 알고리즘 (Z Algorithm)

· 수정 · 📖 약 3분 · 952자/단어 #algorithm #string #z-algorithm
Z-algorithm, z algorithm, z function, z-function, Z 함수

정의

Z 알고리즘 (Z Algorithm) 은 문자열 s[0..N-1]각 위치 i 에 대해, s[i..]s[0..] 의 최장 공통 접두사 (LCP) 길이O(N) 시간에 모두 계산하는 선형 알고리즘.

결과 배열 Z[i] 는:

Z[i] = max { k : s[0..k-1] == s[i..i+k-1] }
Z[0] 은 정의되지 않거나 N (전체 길이)

KMP 와 비슷한 역할 (패턴 매칭 O(N+M)) 이지만, 구현이 더 직관적이고 짧아서 대회에서 선호됨.

문제 상황과 동기

길이 M 패턴 P 를 길이 N 텍스트 S 에서 찾는다.

  • naive: 모든 위치 i 에서 P 와 S[i..] 를 비교. O(N·M).
  • KMP: O(N+M), 하지만 failure function 구현이 복잡.
  • Z 알고리즘: s = P + '$' + S (delimiter) 로 합친 후 Z 배열 계산. Z[i] == M 인 i 가 매칭 위치. O(N+M), 코드 ~20줄.

핵심 통찰: 이미 매칭된 구간 (Z-box) 을 최대한 재사용. amortized O(N).

시각화

핵심 아이디어

Z-box: 위치 i 까지 계산할 때, [l, r] 구간은 “가장 오른쪽으로 뻗은 매칭 구간” (s[l..r] == s[0..r-l]).

invariant: r = max { i + Z[i] - 1 } (지금까지)

새 i 계산:

  1. i > r: naive 비교 시작, Z[i] 갱신, (l, r) 갱신.
  2. i ≤ r: s[i..r]s[i-l..r-l] 와 일치 (Z-box 내부). Z[i-l] 을 재사용:
    • Z[i-l] < r - i + 1: Z[i] = Z[i-l] (재사용 가능)
    • Z[i-l] ≥ r - i + 1: r 너머 확장 시도 필요, naive 비교로 연장

amortized O(N): r 은 최대 N-1 까지 증가, 한 번 증가하면 다시 감소 안 함. 총 비교 횟수 O(N).

알고리즘

z_function(s):
    N = len(s)
    Z = [0] * N
    l, r = 0, 0
    for i = 1 to N-1:
        if i > r:
            k = 0
            while i + k < N and s[k] == s[i + k]:
                k++
            Z[i] = k
            if k > 0:
                l, r = i, i + k - 1
        else:
            k = i - l
            if Z[k] < r - i + 1:
                Z[i] = Z[k]
            else:
                k = r - i + 1
                while i + k < N and s[k] == s[i + k]:
                    k++
                Z[i] = k
                l, r = i, i + k - 1
    return Z

구현

// Z algorithm: O(N) LCP of s[i..] and s[0..]
#include <bits/stdc++.h>
using namespace std;
vector<int> z_function(const string& s) {
  int n = s.size();
  vector<int> z(n);
  int l = 0, r = 0;
  for (int i = 1; i < n; i++) {
      if (i > r) {
          l = r = i;
          while (r < n && s[r - l] == s[r]) r++;
          z[i] = r - l; r--;
      } else {
          int k = i - l;
          if (z[k] < r - i + 1) z[i] = z[k];
          else {
              l = i;
              while (r < n && s[r - l] == s[r]) r++;
              z[i] = r - l; r--;
          }
      }
  }
  return z;
}
int main() {
  string s; cin >> s;
  auto z = z_function(s);
  for (int i = 1; i < (int)s.size(); i++)
      cout << z[i] << (i + 1 == s.size() ? "\n" : " ");
}
stdin
aaabaab
결과
2 1 0 2 1 0

복잡도

항목
시간O(N) (amortized, r 이 한 방향으로만 증가)
공간O(N) (Z 배열)
비교 횟수≤ 2N (각 위치 최대 한 번 방문)

패턴 매칭 응용

길이 M 패턴 P, 길이 N 텍스트 S:

string s = P + "$" + S;  // '$' 는 P, S 어디에도 없는 delimiter
auto z = z_function(s);
for (int i = M + 1; i < (int)s.size(); i++) {
    if (z[i] == M) {
        // i - M - 1 이 S 에서의 매칭 시작 위치
    }
}

O(N+M) 시간, O(N+M) 공간.

변형 / 활용

1. 최장 접두사 = 접미사

s + s 의 Z 배열에서 Z[i] + i == 2N 인 i 찾기. O(N).

2. 회문 판정

s + reverse(s) 의 Z 배열. 하지만 매내처 가 더 빠름.

3. 주기 찾기

Z[i] ≥ N - i 인 가장 작은 i. 문자열의 최소 주기 길이 i.

4. 다중 패턴 매칭

P1 + ’$’ + P2 + ’#’ + S 같은 방식으로 여러 패턴 동시 처리. 하지만 Aho-Corasick 이 더 효율적.

함정

1. Z[0] 정의

Z[0] 은 미정의 또는 N. 코드에 따라 0 또는 N. 보통 쓰지 않음.

2. delimiter 누락

패턴 매칭 시 P + S 로 합치면 P 끝과 S 시작이 이어져서 오판. 반드시 P + '$' + S 로 구분.

3. r < l 상태

r 이 i-1 보다 작을 때 i > r 조건 실패하면 l, r 갱신 안 되어 버그. 구현 정확히.

4. 중복 비교

while (r < n && s[r-l] == s[r]) r++ 루프가 amortized O(N) 임을 신뢰하지 못하고 불필요한 최적화 시도. 그대로 둬야 함.

KMP vs Z 알고리즘

비교KMPZ
복잡도O(N+M)O(N+M)
코드 길이~40줄 (failure func + search)~20줄
직관성failure function 이해 어려움Z-box 재사용 명확
응용오토마타 기반 확장 가능prefix 관련 문제 직접 해결
대회 선호낮음높음 (간결)

대부분의 경우 Z 가 더 간단하므로 KMP 대신 Z 사용 권장.

BOJ 연습 문제

번호제목정답률링크
BOJ 16916부분 문자열53.1%kokoa-lab
BOJ 11585속타는 저녁 메뉴45.2%kokoa-lab
BOJ 1701Cubeditor38.9%kokoa-lab
BOJ 13576Prefix와 Suffix32.1%kokoa-lab

참고

이 글의 용어 (4개)
매내처 알고리즘 (Manacher's Algorithm)algorithm
정의 매내처 알고리즘 (Manacher's Algorithm) 은 문자열 의 모든 위치에서 중심으로 하는 최장 회문 (palindrome) 반지름 을 O(N) 시간에 계산하는 선…
문자열 해싱 (String Hashing)algorithm
정의 문자열 해싱 (String Hashing) 은 문자열을 고정 길이의 정수 (해시값) 로 매핑하여, 부분 문자열 비교를 O(1) 에 수행할 수 있게 만드는 기법. Rabin과…
Aho-Corasick (아호-코라식)algorithm
정의 Aho-Corasick 은 Alfred Aho 와 Margaret Corasick 이 1975년 고안한 다중 패턴 문자열 매칭 알고리즘. Trie 에 KMP failure …
KMP 문자열 매칭 (Knuth-Morris-Pratt)algorithm
정의 KMP (Knuth-Morris-Pratt) 는 Donald Knuth, James H. Morris, Vaughan Pratt 가 1977년 고안한 선형 시간 문자열 매칭…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기