본문으로 바로가기
제품데스크톱 도구TASTECAD도면 확인TASTEFILES파일 관리TASTEFTP파일 전송TASTESSH원격 작업공간TASTEZIP압축과 해제개발 도구TASTECODERust 통합 개발 환경TASTESTUDIO통합 개발·테스트 환경TASTEETL데이터 통합(ETL)서버TASTEREMOTE원격 데스크톱 서버TASTEGEO공간정보 서버TASTEMAIL메일 작업공간시작하기다운로드문서구매모든 제품
마켓플레이스
소식
개발소식오늘의 개발 소식개발 문서프로그래밍 언어RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron알고리즘알고리즘 기초알고리즘 핵심알고리즘 심화실무 알고리즘
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스커뮤니티 안내
협업기술 전수홈페이지 구축데스크톱 앱 구축릴리스·결제 파이프라인판매 파트너문의
서비스원격 데스크톱설치 패키지 커스텀
TASTEDEV
제품

데스크톱 도구

TASTECAD복잡한 도면을 가볍게 열다TASTEFILES필요한 파일만, 가장 빠르게TASTEFTP파일이 오가는 가장 단정한 길TASTESSH서버와 나 사이, 더 단단한 연결TASTEZIP더 빠르게 묶고, 더 안전하게 풀다

개발 도구

TASTECODERust 앱을 편집 · 빌드 · 디버깅 · 테스트까지 한 창에서TASTESTUDIO편집부터 원격 테스트와 승인 기반 AI 수정까지TASTEETL값을 보며 데이터를 옮깁니다

서버

TASTEREMOTE우리 회사 서버로 운영하는 원격 데스크톱TASTEGEOGeoServer 호환 공간정보 서버를 Rust로 다시 세웁니다TASTEMAIL메일의 주도권을 다시 내 손에

시작하기

다운로드모든 제품 최신 판문서제품 사용법구매구독 · 영구 라이선스모든 제품모든 제품 한눈에
모든 제품
마켓플레이스소식
개발

소식

오늘의 개발 소식언어 · 프레임워크 · GitHub · AI 공식 발표개발 문서언어 · 프레임워크 · 알고리즘 안내서

프로그래밍 언어

RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart

프레임워크

프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular
서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express
앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron

알고리즘

알고리즘 기초복잡도 분석 · 정렬 · 탐색 · 기본 자료구조 · 트리와 힙알고리즘 핵심그래프 탐색 · 최단 경로와 최소 신장 트리 · 동적 계획법 · 탐욕 알고리즘 · 분할 정복 · 백트래킹알고리즘 심화문자열 알고리즘 · 수학과 정수론 · 비트 연산 · 구간 트리 · 유니온 파인드실무 알고리즘코딩 면접 풀이 전략 · LRU 캐시 · 레이트 리미터 · 일관된 해시
개발 문서 전체 보기
커뮤니티
프로젝트빌더와 작업 발견개발 로그결정과 시행착오프롬프트재사용 가능한 지시AI 도구 이슈Claude · Codex · Gemini 문제와 우회법쇼케이스완성된 결과커뮤니티 안내공개하고 함께 만드는 방식
협업
기술 전수Rust·egui 네이티브 앱 만들기홈페이지 구축10개 언어, 계정, 문서·다운로드데스크톱 앱 구축Rust·egui, WebView 없이릴리스·결제 파이프라인서명 · MSI·deb·rpm·AppImage · 해시판매 파트너소개하고 결제액의 30% 받기문의구축 · 제휴 · 투자 문의
서비스
원격 데스크톱브라우저로 내 컴퓨터에 접속설치 패키지 커스텀필요한 기능만, 설정은 미리
구매
KO
한국어EnglishDeutschEspañolFrançaisItalianoPortuguês (Brasil)日本語简体中文繁體中文
로그인
로그인
TASTEDEV

작은 도구부터, 더 좋은 개발까지.

support@tastedev.netTASTEDEV 후원하기
제품제품군구매문서다운로드마켓플레이스고객 지원
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스
정책개인정보처리방침이용약관구독 약관환불 정책커뮤니티 운영 정책
계정로그인가입하기
방문자
오늘
012345678901234567890123456789012345678901234567890123456789
어제
012345678901234567890123456789012345678901234567890123456789
누적 조회
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
상호
지엑스소프트
대표자
최영구
사업자등록번호
853-44-00368
주소
인천광역시 미추홀구 경인로 351 9층 V24
전화
+82-10-4404-1523
이메일
whitecalvin@tastedev.net
개인정보 보호책임자
최영구

© 2026 TASTEDEV제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

문자열 알고리즘 문서
문자열 알고리즘
개념동작 원리구현복잡도연습 문제실무 활용
토론

문자열 알고리즘 안내서 · 4/6

복잡도

이 장에서는 텍스트 길이 n, 패턴 길이 m, 알파벳 크기 σ를 기준으로 각 알고리즘의 시간과 공간을 분석합니다. 문자열 알고리즘은 평균과 최악의 차이가 큰 경우가 많으므로 두 경우를 함께 봅니다.

순진한 매칭

시작 위치가 n - m + 1개이고 위치마다 최대 m번 비교하므로 최악은 O(n·m)입니다. 텍스트 aaaa...a와 패턴 aa...ab가 대표적인 최악 입력입니다. 반대로 알파벳이 크고 텍스트가 무작위에 가까우면 대부분 첫 글자나 두 번째 글자에서 실패하므로 평균은 O(n)에 가깝습니다. 추가 메모리는 O(1)입니다.

KMP가 선형인 이유

KMP의 while 루프는 언뜻 보면 중첩 루프처럼 보이지만 전체 비용은 O(n + m)입니다. 상각(amortized) 분석으로 보이면 다음과 같습니다.

  1. k는 텍스트 한 글자마다 최대 1만큼 늘어나므로 전체 증가량은 많아야 n입니다.
  2. while 루프는 돌 때마다 k를 최소 1만큼 줄입니다. k는 0 아래로 내려가지 않습니다.
  3. 따라서 while이 도는 총 횟수는 전체 증가량, 즉 n을 넘을 수 없습니다.

접두사 함수 계산에도 같은 논리가 적용되어 O(m)입니다. 공간은 pi 배열 O(m)이며, 텍스트를 한 번만 앞으로 읽으므로 스트림에도 그대로 쓸 수 있습니다.

비교 횟수를 직접 세어 보면 차이가 잘 드러납니다.

def naive_comparisons(text: str, pattern: str) -> int:
    count = 0
    for s in range(len(text) - len(pattern) + 1):
        for j in range(len(pattern)):
            count += 1
            if text[s + j] != pattern[j]:
                break
    return count


def kmp_comparisons(text: str, pattern: str) -> int:
    pi = [0] * len(pattern)
    k = 0
    for i in range(1, len(pattern)):
        while k > 0 and pattern[i] != pattern[k]:
            k = pi[k - 1]
        if pattern[i] == pattern[k]:
            k += 1
        pi[i] = k
    count, k = 0, 0
    for ch in text:
        while True:
            count += 1
            if ch == pattern[k]:
                k += 1
                break
            if k == 0:
                break
            k = pi[k - 1]
        if k == len(pattern):
            k = pi[k - 1]
    return count


text, pattern = "a" * 10_000, "a" * 99 + "b"
print(naive_comparisons(text, pattern))  # 990100
print(kmp_comparisons(text, pattern))    # 19901

같은 입력에서 순진한 방법은 약 99만 번, KMP는 약 2만 번 비교합니다. KMP의 비교 횟수는 항상 2n 이하입니다.

Rabin-Karp

해시를 굴리는 비용은 창마다 O(1)이므로 해시 계산 전체는 O(n + m)입니다. 해시가 같을 때마다 O(m) 확인을 하므로 실제 출현이 occ번이고 충돌이 드물다면 기대 시간은 O(n + m + occ·m)입니다. 모듈러 M이 작거나 입력이 충돌을 노리고 만들어졌다면 거의 모든 창에서 확인이 일어나 최악 O(n·m)이 됩니다. 공간은 O(1)입니다.

Rabin-Karp가 빛나는 곳은 길이가 같은 패턴 여러 개를 찾을 때입니다. 패턴의 해시를 집합에 넣어 두면 창마다 집합 조회 한 번으로 끝나므로, 패턴 k개를 찾는 데 O(n + k·m) 기대 시간이 듭니다.

def multi_rabin_karp(text: str, patterns: set[str], base: int = 911382323, mod: int = (1 << 61) - 1) -> list[tuple[int, str]]:
    m = len(next(iter(patterns)))
    assert all(len(p) == m for p in patterns)
    def h(s: str) -> int:
        v = 0
        for ch in s:
            v = (v * base + ord(ch)) % mod
        return v
    wanted = {h(p) for p in patterns}
    high = pow(base, m - 1, mod)
    out, cur = [], h(text[:m])
    for i in range(len(text) - m + 1):
        if cur in wanted and text[i:i + m] in patterns:
            out.append((i, text[i:i + m]))
        if i + m < len(text):
            cur = ((cur - ord(text[i]) * high) * base + ord(text[i + m])) % mod
    return out


print(multi_rabin_karp("the cat sat on the mat", {"cat", "mat", "dog"}))
# [(4, 'cat'), (19, 'mat')]

트라이

길이 L인 단어의 삽입, 검색, 접두사 이동은 모두 O(L)이며 사전에 단어가 몇 개 있는지와 무관합니다. 자동 완성은 접두사까지 내려가는 O(L)에 결과를 모으며 방문하는 노드 수가 더해집니다. 공간은 노드 수가 최대 전체 글자 수 N이므로, 자식을 길이 σ 배열로 두면 O(N·σ), 해시 맵으로 두면 O(N)입니다. 단어가 공통 접두사를 적게 공유하면 노드가 많아지므로, 한 갈래로만 이어지는 노드를 합친 압축 트라이(radix tree)를 씁니다.

정렬된 리스트와 이진 탐색으로도 접두사 검색을 할 수 있습니다. 단어 목록이 거의 바뀌지 않는다면 메모리 면에서 더 유리합니다.

import bisect

words = sorted(["car", "cart", "cat", "dog", "dot"])
lo = bisect.bisect_left(words, "ca")
hi = bisect.bisect_left(words, "cb")   # "ca" 다음 접두사
print(words[lo:hi])  # ['car', 'cart', 'cat']

비교 표

알고리즘전처리검색(최악)검색(평균)추가 공간특징
순진한 매칭없음O(n·m)O(n) 가까이O(1)구현이 가장 쉬움
KMPO(m)O(n)O(n)O(m)텍스트를 뒤로 읽지 않음
Z 알고리즘O(n + m)O(n + m)O(n + m)O(n + m)구현이 짧고 응용이 많음
Rabin-KarpO(m)O(n·m)O(n + m)O(1)여러 패턴, 부분 문자열 해시
Boyer-Moore 계열O(m + σ)O(n·m)선형보다 작을 수 있음O(m + σ)긴 패턴, 큰 알파벳에서 빠름
Aho-CorasickO(전체 패턴 길이)O(n + 출현 수)같음패턴 크기 비례여러 패턴을 한 번에
트라이O(N)질의당 O(L)같음O(N)~O(N·σ)사전 접두사 검색

정리

  • KMP와 Z는 최악에도 선형이며, 증가량으로 감소량을 묶는 상각 분석이 핵심입니다.
  • Rabin-Karp는 기대 선형이지만 충돌에 따라 최악 O(n·m)이 될 수 있습니다.
  • 트라이 연산은 단어 길이에만 비례하지만 메모리 비용이 크므로 압축 트라이나 정렬 리스트도 함께 고려합니다.
이 장에 대해 질문이 있나요?
이전 장구현다음 장연습 문제

댓글 0개

인기 댓글순최신순

로그인 · 로그인하면 댓글을 남길 수 있습니다.