2 분 소요

1. 트라이의 정의와 필요성

1.1 트라이란?

  • 정의: 문자열을 저장하고 검색하는데 사용되는 트리 자료구조
  • 특징:
    • 각 노드는 하나의 문자를 저장
    • 루트에서 리프까지의 경로가 하나의 문자열을 나타냄
    • 문자열 검색에 O(m) 시간 복잡도 (m은 문자열 길이)

1.2 트라이의 장단점

  • 장점:
    • 빠른 탐색 시간 O(N) (N=문자열 길이)
    • Hash와 같은 시간 복잡도
    • BST 동작을 응용해서 더 많은 동작 가능
  • 단점:
    • 큰 메모리 요구 O(M) (M=삽입된 문자열의 총 길이)
    • O(포인터크기 * 포인터배열개수 * 노드개수)
    • 예: 1000자 문자열 500개 = 50만개 노드 -> 약 100MB 메모리

2. 트라이 구조

2.1 노드 및 간선

  • 노드 구조:
    • 각 노드는 <Key, Value> Map을 가짐
    • Key는 알파벳
    • Value는 이어지는 자식노드
    • 노드 자체보다는 노드에서 이어진 간선이 문자를 가짐
  • 특징:
    • root에서 leaf node까지 닿지 못하면, 원래 문자열 집합을 전부 뽑아낼 수 없음
    • 코딩상에서 bool 변수 isLastChar 필요
    • 부모가 같은 자식 노드들은 공통 접두어를 가짐
    • 같은 노드에서 여러 간선이 나올 수 있으나, 서로 다른 라벨(문자)
    • 두 문자열의 접두사가 같다면 그 것만큼 트리에서 공유

3. 트라이 구현

3.1 C++ 구현

class TrieNode {
public:
    TrieNode* children[26];  // 알파벳 소문자만 가정
    bool isEndOfWord;
    
    TrieNode() {
        for (int i = 0; i < 26; i++) {
            children[i] = nullptr;
        }
        isEndOfWord = false;
    }
};

class Trie {
private:
    TrieNode* root;
    
public:
    Trie() {
        root = new TrieNode();
    }
    
    void insert(const string& word) {
        TrieNode* current = root;
        for (char c : word) {
            int index = c - 'a';
            if (current->children[index] == nullptr) {
                current->children[index] = new TrieNode();
            }
            current = current->children[index];
        }
        current->isEndOfWord = true;
    }
    
    bool search(const string& word) {
        TrieNode* current = root;
        for (char c : word) {
            int index = c - 'a';
            if (current->children[index] == nullptr) {
                return false;
            }
            current = current->children[index];
        }
        return current->isEndOfWord;
    }
    
    bool startsWith(const string& prefix) {
        TrieNode* current = root;
        for (char c : prefix) {
            int index = c - 'a';
            if (current->children[index] == nullptr) {
                return false;
            }
            current = current->children[index];
        }
        return true;
    }
};

3.2 Java 구현

import java.util.HashMap;
import java.util.Map;

public class TrieNode {
    private Map<Character, TrieNode> childNodes = new HashMap<>();
    private boolean isLastChar;
    
    Map<Character, TrieNode> getChildNodes() {
        return this.childNodes;
    }
    
    boolean isLastChar() {
        return this.isLastChar;
    }
    
    void setIsLastChar(boolean isLastChar) {
        this.isLastChar = isLastChar;
    }
}

class Trie {
    private TrieNode rootNode;
    
    Trie() {
        rootNode = new TrieNode();
    }
    
    void insert(String word) {
        TrieNode thisNode = this.rootNode;
        for (int i = 0; i < word.length(); i++) {
            thisNode = thisNode.getChildNodes().computeIfAbsent(word.charAt(i),
                    c -> new TrieNode());
        }
        thisNode.setIsLastChar(true);
    }
    
    boolean contains(String word) {
        TrieNode thisNode = this.rootNode;
        for (int i = 0; i < word.length(); i++) {
            char character = word.charAt(i);
            TrieNode node = thisNode.getChildNodes().get(character);
            if (node == null)
                return false;
            thisNode = node;
        }
        return thisNode.isLastChar();
    }
    
    void delete(String word) {
        delete(this.rootNode, word, 0);
    }
    
    private void delete(TrieNode thisNode, String word, int index) {
        char character = word.charAt(index);
        if(!thisNode.getChildNodes().containsKey(character))
            throw new Error("There is no [" + word + "] in this Trie.");

        TrieNode childNode = thisNode.getChildNodes().get(character);
        index++;

        if(index == word.length()) {
            if (!childNode.isLastChar())
                throw new Error("There is no [" + word + "] in this Trie.");
            childNode.setIsLastChar(false);

            if (childNode.getChildNodes().isEmpty())
                thisNode.getChildNodes().remove(character);
        } else {
            delete(childNode, word, index);
            if(!childNode.isLastChar() && childNode.getChildNodes().isEmpty())
                thisNode.getChildNodes().remove(character);
        }
    }
}

4. 트라이의 응용

4.1 자동완성

  • 구현 방식:
    • 트라이에 단어들을 저장
    • 접두어로 검색하여 일치하는 모든 단어 찾기
  • 장점:
    • 빠른 검색 속도
    • 메모리 효율적

4.2 철자 검사기

  • 구현 방식:
    • 사전의 단어들을 트라이에 저장
    • 입력된 단어가 사전에 있는지 검사
  • 장점:
    • 실시간 검사 가능
    • 오타 수정 제안 가능

댓글남기기