| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- es6
- literal
- 0.5px border
- angular
- &연산
- 타입스크립트
- 1px border
- ES5
- ZOOM
- jwt
- 전역변수
- TS
- 컴포넌튼
- Websocket
- 10px
- 으
- 데이터베이스 #try #이중
- 0.75px border
- 서버리스 #
- Props
- entity
- npm
- 문서번호
- 클론코딩
- 당근마켓
- font-size
- Strict
- TypeScript
- 0.25px border
- github
- Today
- Total
복잡한뇌구조마냥
[알고리즘] 아호 코라식 (Aho-Corasick) 본문
여러개의 문자열 패턴을 단순하게 순회하면서 비교해서는 알고리즘 문제의 시간초과를 감당할 수 없어서 찾은 알고리즘 방식이
아호 코라식이다.
Aho-Corasick 알고리즘
- 여러개의 문자열 패턴을 한 번에 빠르게 텍스트에서 찾을 수 있게 해주는 알고리즘이다.
- 해당 알고리즘은 간단하게 얘기하면 KMP + Trie + BFS 가 결합된 구조로, 여러패턴을 동시에 검색할 수 있는 효율적인 알고리즘이다.
- 비교하고 싶은 문자열이 여러개일 때, 트라이 구조로 패턴 문자를 삽입하여 비교한다.
원리를 많이 설명하는데 해당 사진을 많이 사용해서 참고자료로 가져와봤다.

- 노드가 처음시작할 때 h냐 s냐에 따라 갈 수 있는 방향이 다음과 같이 정해진다.
- 만약에 he에서 글자가 끝나지만 찾는 문자가 hers라면 rs까지 도달해야 원하는 문자열이 반환된다.
- 해당 구조를 잘 이용하면 자동완성기능에도 유용하게 사용이 가능하다.
- 예를 들어, h만 있을 때 he, hers, his를 모두 반환하고 i로 넘어가면 his만 반환하도록 만들어서 만들어 질 수 있는 모든 경우의 수를 output으로 반환하는 방식으로도 사용 할 수도 있다.
아래 문제는 백준의 한 문제를 예시로 가져왔다.
class Node {
constructor() {
this.children = {}; // 자식 노드들 (Trie)
this.fail = null; // 실패 링크
this.output = []; // 이 노드에서 끝나는 패턴들
}
}
class AhoCorasick {
constructor() {
this.root = new Node();
}
// 1. 패턴 삽입
insert(pattern) {
let node = this.root;
for (const char of pattern) {
if (!node.children[char]) {
node.children[char] = new Node();
}
node = node.children[char];
}
node.output.push(pattern);
}
// 2. 실패 링크 구성
buildFailureLinks() {
const queue = [];
for (const char in this.root.children) {
const child = this.root.children[char];
child.fail = this.root;
queue.push(child);
}
while (queue.length > 0) {
const current = queue.shift();
for (const char in current.children) {
const child = current.children[char];
let fail = current.fail;
while (fail && !fail.children[char]) {
fail = fail.fail;
}
child.fail = fail ? fail.children[char] : this.root;
child.output = child.output.concat(child.fail.output);
queue.push(child);
}
}
}
// 3. 문자열에 패턴이 포함되어 있는지 탐색
containsAny(text) {
let node = this.root;
for (const char of text) {
while (node && !node.children[char]) {
node = node.fail;
}
node = node ? node.children[char] || this.root : this.root;
if (node.output.length > 0) return true;
}
return false;
}
}
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin').toString().trim().split('\n');
const n = parseInt(input[0]);
const patterns = input.slice(1, 1 + n);
const q = parseInt(input[n + 1]);
const queries = input.slice(n + 2);
// AC 트리 구축
const ac = new AhoCorasick();
for (const pattern of patterns) {
ac.insert(pattern);
}
ac.buildFailureLinks();
// 질의 처리
const result = queries.map(str => ac.containsAny(str) ? 'YES' : 'NO');
console.log(result.join('\n'));
1. AhoCorasick 클래스에 트라이 구조를 바탕으로 패턴을 삽입하고, 각 패턴의 마지막에 해당하는 값을 output으로 반환한다.
2. 실패 링크(fail)을 설정한다. BFS 로 Trie를 순회하면서 각 노드에 실패할 시 이동할 노드를 설정한다.
- KMP 의 실패함수와 비슷한 역할이라고 생각된다.
3. 텍스트를 한글자씩 보면서 매칭이 되는 각각의 트라이로 이동해서 실패하면 fail로 점프하고, 성공하면 마지막에 output에 문자를 처리한다.
4. AhoCorasick을 처리할 수 있는 클래스를 만들었으면, 해당 클래스에 패턴을 삽입하여, 구조를 연결한다.
5. 특정 문자열을 AhoCorasick에 넣어서 해당 문자열에 맞는 패턴이 존재하는지 확인한다.
이 구조를 이요하면 모든 패턴을 한 번의 탐색으로 동시에 검색이 가능해서, 시간복잡도가 엄청나게 줄어든다.
최근에 코딩 테스트를 봤는데 미리 준비했다면 조금 더 좋은 결과를 볼 수 있었을 것 같다는 아쉬움이 있다.
참고자료 :
https://m.blog.naver.com/kks227/220992598966
아호코라식(Aho-Corasick)
안녕하세요. 진짜 강좌를 이제 두 달만에... 메이플 개꿀잼이네요. 문자열 파트 글이 두 개 남았는데, 모두...
blog.naver.com
'공통 > 알고리즘 및 코테' 카테고리의 다른 글
| [자료구조] 개념 정리 (7) | 2025.05.18 |
|---|---|
| [알고리즘] 다익스트라 (Dijkstra`s Algorithm) (0) | 2025.05.17 |
| [코딩테스트] 백준 JavaScript 입력값 받기 (0) | 2025.05.10 |
| [알고리즘] BFS (너비 우선 탐색) (0) | 2025.05.06 |
| [알고리즘] 2주차 프로그래밍 기초 협업 내용 정리 (0) | 2022.08.15 |