알고리즘 & 자료구조

[자료구조] Trie - Swift

나른한코딩 2022. 2. 25. 15:59

Trie

문자열 검색 문제에 특화된 트리 형태의 자료구조. 
원하는 문자열을 O(m) (m = 문자열의 길이) 시간복잡도로 찾을 수 있다.

  • struct Trie 내의 Trie 포인터 배열을 가지고 해당 키에 맞는 포인터로 이어지는 구조.
  • Trie는 검색 후보 문자열을 하나의 트리로 만들어서, 한번의 검색 O(m) 만으로 문자열의 존재 여부를 검색 가능하다.

주의

  • 항상 첫번째 root 노드는 빈칸이다.
  • 트라이 객체마다 트라이 포인터 배열을 경우의 수만큼 가져야 하므로 공간복잡도가 매우 커질 수 있다.
  • 공간복잡도 : O(key의 경우의수 * 포인터 크기 * 전체 트라이에 존재하는 노드 수)

필수

1) 다음 두 가지를 가지고 있어야 한다.

- child (자식 노드를 담는 배열)
- isTerminal (어떤 단어의 마지막에 해당하는 노드인지 여부를 나타낸다.)
  • 노드를 탐색하다가 isTermial == true이면 지금까지 거친노드를 연결한 온전한 단어가 존재한다는 뜻이다.
    2) class로 구현해야 한다.
  • struct는 자기 자신의 타입을 갖지 못함.
  • 노드(trie)는 child, isTerminal을 가지는데, child는 trie 배이므로 자기 자신을 가져야 한다.

Trie class 구현

class  Trie {
    private var child: [Trie?] = Array(reapeating: nil, count: 26) // 알파벳 갯수 만큼 count
    private var isTerminla = false
}
  • child[0] = a, child[1] = b .... 를 의미한다.
  • child[0] == nil : 지금의 알파벳 다음 알파벳으로 a가 올 수 없다.
  • child[0] != nil : 지금의 알파벳 다음에는 a가 존재하며 연결되어 있다.

Trie에 단어 저장

// 외부에서 편하게 사용하기 위해 작성
func insert(_ s: String) {
    let array: [Character] = Array(s)
    insertCharacter(array, 0)
}

// 입력받은 s를 Character 배열로 만들어, s의 길이 만큼 trie의 child에 접근하여 재귀호출하는 함수
private func insertCharacter(_ array: [Character], _ index: Int) {
    // 모든 단어를 탐색했다면 isTerminal을 true로 변경
    if array.count == index {
        self.isTerminal = true
        return
    }

    let charIndex = toNumber(array[index])

    if child[charIndex] == nil {
        child[charIndex] = Trie()
    }

    child[charIndex]?.insertCharacter(array, index + 1)
}

// subscript에 사용하기 위해 문자를 0-25 사이의 숫자로 바꾸어 리턴하는 함수
func toNumber(_ cha : Character ) -> Int {
    return Int(cha.asciiValue! - Character("a").asciiValue!)
}

특정 단어 찾기

// 외부에서 편하게 사용하기 위해 작성
func find(_ s: String) -> Bool {
    let array: [Character] = Array(s)
    return find(Character(array, 0))
}

// 탐색하다가 특정단어가 없다면 child[charIndex] == nil 이므로, falase 리턴
// 특정 단어를 다 탐색 후 isTerminal이 true이면 해당한어를 찾았기에 true 리턴
private func findCharacter(_ array: [Character], _ index: Int) -> Bool {
    if array.count == index {
        if isTerminal { return true }
        return false
    }

    let charIndex = toNumber(array[index])
    if child[charIndex] == nil { return false }

    return child[charIndex]!.findCharacter(array, index + 1)
}

장점

  • 검색 시간이 단축된다.
    • 여러번의 쿼리를 요구하는 문제에서 O(m)의 시간복잡도로 결과를 얻을 수 있다.

단점

  • 문자열의 수가 많아지거나 길어질수록 메모리를 많이 차지한다.
    • 이유: 각 노드의 child가 알파벳 갯수만큼을 항상 가지고 있기 때문

응용

  • 원하는 prefix를 가진 단어의 존재 여부 파악하기
  • 단어의 입력 순서 기억하기

참조 링크

반응형

'알고리즘 & 자료구조' 카테고리의 다른 글

BFS/DFS 간단 예제 - Swift  (0) 2022.02.26
Stack / Queue 구현해보기 - swift  (0) 2021.08.20
Bubble Sort 간단 예제 - swift  (0) 2021.07.16