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

정규 표현식 (Regular Expression, Regex)

· 수정 · 📖 약 2분 · 768자/단어 #algorithm #string #regex #nfa #dfa
regex, regular expression, 정규 표현식, 정규식, NFA, DFA

정의

정규 표현식 (Regular Expression) 은 문자열 패턴을 형식 언어로 표현한 표기법. Stephen Kleene 이 1956년 정규 언어 (regular language) 를 정의하며 도입. Ken Thompson 이 1968년 이를 NFA 로 변환하는 알고리즘 (Thompson construction) 을 구현해 Unix grep, ed 에 탑재. RE → NFA → DFA (선택) → 매칭.

문제 상황과 동기

"ab*c" 패턴: "ac", "abc", "abbc", ... 모두 매칭.

  • naive: 모든 경우의 수 직접 확인. * (0회 이상) 때문에 지수적 폭발.
  • Thompson NFA: 정규식 길이 |R| 에 대해 O(|R|) 크기 NFA 생성. 매칭 O(N x |R|) (NFA simulation) 또는 O(N) (DFA 변환 후).

핵심 통찰: Kleene star * 는 NFA 의 epsilon 전이로 구현. Thompson construction 은 정규식 각 부분을 소형 NFA 조각으로 만들고 epsilon 으로 연결.

시각화

핵심 아이디어

기본 연산

연산표기언어
문자a{a}
연결ab{ab}
선택a|b{a, b}
반복a*{ε, a, aa, ...}
그룹(ab)하위 표현식

Thompson Construction & NFA Simulation

a: ─→[○]─a─→[●]    AB: [A]─ε─→[B]
A|B: ─→[A]─→ / └→[B]─→    A*: ─→[A]─→ with ε loop back

NFA Simulation:

현재 가능한 상태 집합 유지. 각 문자마다 epsilon closure 계산 후 문자 전이. O(N x |R|). DFA 로 변환 (powerset construction) 시 O(N) 매칭 (공간 2^|R| 최악).

알고리즘

re_to_nfa(regex):
    postfix = shunting_yard(regex)
    stack = []
    for t in postfix:
        if t is char: stack.push(NFA_fragment(t))
        elif t == '.': b=stack.pop(); a=stack.pop()
            a.accept.out = b.start; stack.push(NFA(a.start, b.accept))
        elif t == '|': b=stack.pop(); a=stack.pop()
            s = State(); s.out=a.start; s.out1=b.start
            acc = State(); a.accept.out=acc; b.accept.out=acc
            stack.push(NFA(s, acc))
        elif t == '*': a = stack.pop()
            s = State(); acc = State()
            s.out=a.start; s.out1=acc
            a.accept.out=a.start; a.accept.out1=acc
            stack.push(NFA(s, acc))
    return stack[0]

구현

// Regex 매칭 (백트래킹 기반, 문자/./*/|/()/연결)
#include <bits/stdc++.h>
using namespace std;
bool match(const string& s, int si, const string& p, int pi) {
  if (pi == p.size()) return si == s.size();
  bool star = pi+1 < p.size() && p[pi+1] == '*';
  if (star) {
      if (match(s, si, p, pi+2)) return true;
      while (si < s.size() && (p[pi]=='.' || s[si]==p[pi])) {
          if (match(s, si+1, p, pi+2)) return true;
          si++;
      }
      return false;
  }
  if (si < s.size() && (p[pi]=='.' || s[si]==p[pi]))
      return match(s, si+1, p, pi+1);
  return false;
}
int main() {
  string p, s; getline(cin, p); getline(cin, s);
  cout << match(s, 0, p, 0) << "\n";
}
stdin
ab*c
ac
결과
1

복잡도

항목
RE to NFA (Thompson)O(
NFA simulation (전체)O(N x
NFA to DFAO(2^
DFA 매칭O(N)

변형 / 활용

  • POSIX vs Perl 호환: ERE 는 +, ?, {m,n}. PCRE 는 역참조, lookahead/lookbehind 포함 (정규 언어 초과).
  • DFA vs 백트래킹: RE2/grep = DFA (선형). PCRE/Python re = 백트래킹 (지수 가능).
  • Lex/Flex: 정규식을 DFA 로 컴파일해 tokenizer 생성.
  • Kleene 정리: 정규 언어는 합집합, 연결, Kleene star 에 닫힘. DFA = 정규 표현식.

함정

  1. 지수적 백트래킹: (a|a)*b 에서 "aaaa...ac" 매칭 시 지수 시간. DFA 엔진은 선형.
  2. 빈 문자열 epsilon: a* 0회 반복 케이스 누락 시 "ac"a*c 에 매칭 안 됨.
  3. 앵커 / 우선순위: ab|c = (ab)|c. 연결 > 선택. 명시적 괄호로 제어.

BOJ 연습 문제

번호제목정답률링크
BOJ 1013Contact26.3%kokoa-lab
BOJ 2870수학숙제40.1%kokoa-lab
BOJ 9342염색체43.8%kokoa-lab
BOJ 2671잠수함 식별28.5%kokoa-lab

참고

이 글의 용어 (3개)
문자열 (String)algorithm
정의 문자열 (String) 은 문자(character)의 순서 있는 나열. 프로그래밍에서는 연속된 메모리에 저장된 문자 배열 로 구현되며, 각 문자는 ASCII(7비트) 또는 …
파싱 (Parsing) - tokenization, recursive descent, expressionalgorithm
정의 파싱 은 입력 문자열을 구조화된 표현 (AST) 으로 변환하는 과정. 컴파일러/인터프리터의 첫 단계. 세 단계: Tokenization (lexing) → Parsing (…
KMP 문자열 매칭 (Knuth-Morris-Pratt)algorithm
정의 KMP (Knuth-Morris-Pratt) 는 Donald Knuth, James H. Morris, Vaughan Pratt 가 1977년 고안한 선형 시간 문자열 매칭…

💬 댓글

사이트 검색 / 명령어

검색

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