본문으로 바로가기
제품데스크톱 도구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제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

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

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

개념

문자열 알고리즘은 글자의 나열을 빠르고 정확하게 다루는 방법을 모은 분야입니다. 편집기의 찾기 기능, 로그 검색, 검색창의 자동 완성, 유전체 서열 비교처럼 "어떤 글자 묶음이 어디에 있는가"를 묻는 일은 모두 문자열 알고리즘 위에서 돌아갑니다. 이 장에서는 이후 장에서 계속 쓸 용어를 정리하고, 가장 단순한 방법인 순진한 매칭부터 출발해 왜 더 나은 알고리즘이 필요한지 살펴봅니다.

기본 용어

용어뜻예(문자열 abcab)
텍스트(text)검색 대상이 되는 긴 문자열, 길이 n로그 파일 전체
패턴(pattern)찾으려는 짧은 문자열, 길이 m검색어
접두사(prefix)앞에서부터 잘라 낸 부분a, ab, abc
접미사(suffix)뒤에서부터 잘라 낸 부분b, ab, cab
경계(border)접두사이면서 접미사인 진부분 문자열ab
출현(occurrence)패턴이 텍스트에서 시작하는 위치0부터 세는 인덱스
알파벳(alphabet)쓰일 수 있는 글자 집합ASCII, DNA의 ACGT

"진(proper)" 접두사는 문자열 전체를 뺀 접두사를 말합니다. 경계는 KMP 알고리즘의 핵심이므로 이 장에서 확실히 익혀 두는 것이 좋습니다.

문제의 종류

문자열 알고리즘이 푸는 문제는 크게 네 가지로 나눌 수 있습니다.

  • 단일 패턴 검색: 텍스트에서 패턴 하나의 모든 출현 위치를 찾습니다. 순진한 방법, KMP, Z 알고리즘, Rabin-Karp, Boyer-Moore가 여기에 속합니다.
  • 다중 패턴 검색: 금칙어 목록처럼 여러 패턴을 한 번에 찾습니다. Aho-Corasick, 해시 집합을 쓰는 Rabin-Karp가 대표적입니다.
  • 사전 검색: 단어 목록에서 특정 접두사로 시작하는 단어를 찾습니다. 트라이(trie)가 전형적인 도구입니다.
  • 텍스트 색인: 같은 텍스트에 질의가 많이 들어올 때 텍스트를 미리 가공해 둡니다. 접미사 배열이나 FM 인덱스가 쓰입니다.

순진한 매칭

가장 직관적인 방법은 텍스트의 모든 시작 위치에서 패턴을 한 글자씩 비교하는 것입니다.

def naive_search(text: str, pattern: str) -> list[int]:
    n, m = len(text), len(pattern)
    hits = []
    for start in range(n - m + 1):
        j = 0
        while j < m and text[start + j] == pattern[j]:
            j += 1
        if j == m:
            hits.append(start)
    return hits


print(naive_search("abracadabra", "abra"))  # [0, 7]

이 방법은 짧은 텍스트에는 충분합니다. 하지만 텍스트가 aaaa...a이고 패턴이 aaa...ab이면 시작 위치마다 거의 m번을 비교한 뒤 실패하므로 최악의 경우 O(n·m)이 됩니다. 문제는 실패할 때마다 이미 비교해서 알게 된 정보를 버리고 다음 위치에서 처음부터 다시 비교한다는 점입니다. KMP는 이 정보를 경계 표로 저장해 재사용하고, Rabin-Karp는 비교 자체를 해시값 비교로 바꿉니다.

경계를 직접 구해 보기

경계 개념을 손에 익히기 위해 가장 긴 경계를 무식하게 구해 봅니다. 다음 장에서 이 값을 O(m)에 구하는 접두사 함수를 다룹니다.

def longest_border(s: str) -> int:
    for length in range(len(s) - 1, 0, -1):
        if s[:length] == s[-length:]:
            return length
    return 0


for word in ["abcab", "aaaa", "abcd", "ababa"]:
    print(word, longest_border(word))
# abcab 2 / aaaa 3 / abcd 0 / ababa 3

ababa의 가장 긴 경계는 aba로 길이 3입니다. 경계는 서로 겹칠 수 있다는 점에 주의합니다.

언제 직접 구현하는가

Python의 in, str.find, str.count, 그리고 정규 표현식 모듈 re는 이미 잘 최적화되어 있습니다. 실무에서 단순 검색을 위해 KMP를 다시 짤 일은 드뭅니다. 그래도 직접 구현을 알아야 하는 경우가 있습니다.

  • 표준 함수가 주지 않는 정보가 필요할 때: 접두사 함수 값, 문자열의 최소 주기, 모든 경계 등
  • 스트림처럼 텍스트가 조금씩 들어와 한 번에 넘길 수 없을 때
  • 여러 패턴, 접두사 검색, 부분 문자열 해시처럼 표준 함수 범위를 벗어날 때
  • 코딩 테스트와 면접에서 시간 복잡도를 설명해야 할 때

유니코드 주의점

"글자"의 정의는 생각보다 단순하지 않습니다. Python의 str은 코드 포인트(code point)의 나열이고, 화면에 보이는 한 글자가 코드 포인트 여러 개일 수 있습니다.

import unicodedata

a = "café"                       # é 하나(U+00E9)
b = "café"                 # e + 결합 악센트(U+0301)
print(a == b, len(a), len(b))    # False 4 5
print(unicodedata.normalize("NFC", a) == unicodedata.normalize("NFC", b))  # True
print(len(a.encode("utf-8")))    # 5 (바이트 수)

검색 전에 텍스트와 패턴을 같은 정규화 형식(NFC 등)으로 맞추지 않으면, 사람 눈에는 같은 단어가 검색되지 않습니다. 이 문제는 마지막 장에서 다시 다룹니다.

정리

  • 텍스트 길이는 n, 패턴 길이는 m으로 쓰며, 경계는 접두사이면서 접미사인 진부분 문자열입니다.
  • 순진한 매칭은 최악 O(n·m)이며, 실패할 때 얻은 정보를 버리는 것이 약점입니다.
  • KMP·Z는 이미 비교한 정보를 재사용하고, Rabin-Karp는 해시로 비교를 줄이며, 트라이는 사전의 접두사 검색을 맡습니다.
  • 유니코드 텍스트는 정규화와 "글자" 단위를 먼저 정해야 결과가 기대와 맞습니다.
이 장에 대해 질문이 있나요?
다음 장동작 원리

댓글 0개

인기 댓글순최신순

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