Trie Datastructure

Matthew Woo·2022년 3월 13일
0

Data structure

목록 보기
3/3
post-thumbnail

리트코드에서 위의 문제를 풀다가 Trie 자료구조에 대해 알게 되었다.

Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]

위의 212번 문제는 주어진 표 들에서 표와 단어들을 주면 해당 표에 주어진 단어들이 있는지 탐색하는 문제다.
dfs로 단어들이 각각 있는지 찾게 되었는데 코드를 작성하면서도 직감할 수 있었다. 시간초과가 날 것이라는 것. 더 효율적으로 풀 방법이 있다는 것을.. 그렇지 않고서야 이게 난이도가 HARD라는 것을.

역시나 결과적으로 시간초과가 발생하였고 Trie 자료구조를 사용하면 이를 개선할 수 있다.

찾아야하는 각 단어별로 매번 board에서 탐색을 하게 되니 시간초과가 발생하였는데 찾을 단어들을 Trie 자료구조로 미리 구성해 둔 다음 board는 단어의 수만큼 탐색했던 것과는 달리 한 번만 탐색하면서 Trie 자료구조에 넣어둔 단어와 일치되는 것이 있는지만 찾아볼 수 있다.


위 문제와는 별개로 Trie 자료구조에서 시간복잡도는 (m) m: 단어의 길이가 된다.
Trie 가 아닌 Binary Search tree로 해당 단어들을 저장해두었을 경우 시간복잡도는 m(단어의 길이) * log n(다음 문자를 탐색). 따라서 Trie 자료구조는 시간복잡도에서 강점을 갖고 있다.

공간복잡도는 어떻게 될까?
단어들이 중복되는 부분이 없다면 다른 BST와 마찬가지로 공간복잡도는 각 단어들 길이의 합 만큼 생성되겠지만, 단어들 중 첫 시작부터 중복되는 부분은 별개로 저장되는 것이 아닌 트리 구조로 중복시킨다. 'apple' , 'applepie' 의 단어를 예로들자면 'applepie' 를 저장하고 중간과 마지막의 'e'를 단어의 끝이라는 점을 마킹해둠으로서 두 단어를 별개로 저장하지 않는다.

그림) 'apple' 'applepie' 'banana' 'bananacake' 'cake' 'juice' 를 저장하는 경우


그럼 메모리 이용률도 좋은걸까? 메모리 이용률이 괜찮아보이지만 실제 사용하게 되었을 때 데이터가 많다면 각 노드에서 다음 노드를 가리키는 포인트가 굉장히 많이 생성된다. 알파벳의 경우 그 다음으로 가는 노드가 26개가 생성된다. 또한 각 노드에서 해쉬로 다음 노드로 연결하며 각 노드에서도 해당 노드가 end포인트를 체크가 필요하기에 시간복잡도는 좋지만 메모리를 많이 잡아먹게 된다.

Trie 자료구조 <- 이 문제는 Trie 자체를 구현하는 문제이다.

# Trie DataStructure
class TrieNode: # 각 노드
    def __init__(self): 
        self.children = {} # 다음 노드로 이어짐
        self.endOfWord = False # 단어의 마지막을 표시하기 위한 마킹. False는 마지막이 아님, True는 마지막
        						# 위 그림에서 노란색, 흰색을 결정하는 부분

class Trie:
    def __init__(self): # 시작 Root 노드 생성
        self.root = TrieNode()

    def insert(self, word: str) -> None: # 단어를 추가하기
        cur = self.root
        
        for c in word: # 각 단어를 하나씩 넣으면서 중복된 것이 아니라면 새롭게 가지치기
            if c not in cur.children:
                cur.children[c] = TrieNode() # 가지치기
            cur = cur.children[c]
        cur.endOfWord = True # 마지막 단어의 경우 마지막이라고 마킹해줌

    def search(self, word: str) -> bool: # 해당 단어가  있는지
        cur = self.root
        
        for c in word: # 단어를 하나씩 타고 내려감
            if c not in cur.children:
                return False # 하나라도 다른게 나오면 False를 Return
            cur = cur.children[c]
        return cur.endOfWord  # 마지막이 해당 단어의 끝이여야 True 를 Return

    def startsWith(self, prefix: str) -> bool: # 해당 prefix로 시작하는 단어가 있는지. 단어의 마지막이 끝지점인지를 확인할 필요가 없음.
        cur = self.root
        
        for c in prefix:
            if c not in cur.children:
                return False
            cur = cur.children[c]
        return True

참고
https://en.wikipedia.org/wiki/Trie
https://www.geeksforgeeks.org/trie-insert-and-search/

profile
지속가능하고 안정적인 시스템을 만들고자 합니다.

0개의 댓글