트라이 이전에는 배열, 해시 테이블, BST, Balanced BST, 문자열 검색 전용 알고리즘(KMP, Boyer-Moore), 그리고 정규 표현식 등으로 효과적으로 문자열 검색이 가능했습니다. 그러나 기존에 사용되던 자료구조로는 문자열 검색에 있어 여러 제약 사항과 성능 이슈가 있었기 때문에, 트라이는 이러한 문제를 해결하기 위한 방법으로 개발되었습니다.
예를 들어서, 배열과 해시테이블로 문자열을 검색하는 경우를 생각해 보겠습니다.
트라이는 1959년에 Edward Fredkin에 의해 처음 소개되었습니다. 그 당시에는 효율적인 문자열 검색, 특히 사전 검색(Dictionary lookup)에 대한 높은 요구가 있었습니다.
트라이(Trie)는 트리의 일종으로 ”문자열의 키”를 효율적으로 저장하고 검색하기 위한 자료구조 입니다. 여기서, 문자열의 키라고 하면 “apple”, “app”, “a”, “application”등이 될 수 있습니다.
다음과 같은 경우에 아주 유용하게 활용할 수 있는 자료구조입니다.
class Node:
def __init__(self):
self.children = {}
self.isEndOfWord = False
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word):
current = self.root
for char in word:
if char not in current.children:
current.children[char] = Node()
current = current.children[char]
current.isEndOfWord = True
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word):
self._insert_recursive(self.root, word)
def _insert_recursive(self, node, word):
if not word:
node.isEndOfWord = True
return
char = word[0]
if char not in node.children:
node.children[char] = Node()
self._insert_recursive(node.children[char], word[1:])
class Trie:
def __init__(self):
self.root = Node()
def search(self, word):
return self._search_recursive(self.root, word)
def _search_recursive(self, node, word):
if not word:
return node.isEndOfWord
char = word[0]
if char in node.children:
return self._search_recursive(node.children[char], word[1:])
else:
return False
class Trie:
def __init__(self):
self.root = Node()
def autocomplete(self, prefix):
current = self.root
for char in prefix:
if char in current.children:
current = current.children[char]
else:
return []
return self._search_start_with(current, prefix)
def _search_start_with(self, node, prefix):
results = []
if node.isEndOfWord:
results.append(prefix)
for char, child in node.children.items():
results.extend(self._search_start_with(child, prefix + char))
return results
autocomplete
함수는 주어진 접두어(prefix)에 해당하는 노드를 찾습니다. 만약 접두어에 해당하는 노드가 트라이 내에 없다면 빈 결과 리스트를 반환합니다._search_start_with
함수는 재귀적으로 동작하며, 현재 노드에서 시작하는 모든 단어를 찾아 결과 리스트에 추가합니다.접두어를 탐색하는 부분은 상수 시간 이 걸리는데 (여기서 p는 접두어의 길이), 이는 전체 트라이의 크기에 비해 상대적으로 작은 값입니다.
재귀적 탐색 부분(_search_start_with())에서는 트라이의 특정 부분 (접두어가 가리키는 노드부터 시작하여 그 아래에 있는 모든 노드들)을 탐색합니다. 최악의 경우, 이 부분이 트라이의 전체 노드를 탐색할 수도 있습니다.
따라서, 시간복잡도를 계산할 때 가장 중요한 것은 이 재귀적 탐색의 부분입니다. 이 부분의 시간복잡도는 입니다. 여기서 n은 탐색되는 트라이 부분의 노드 수입니다.
재귀의 깊이는 트라이의 최대 높이나 깊이에 비례합니다. 트라이의 깊이는 문자열의 최대 길이로 제한됩니다. 이 최대 깊이를 d 라고 할 때, 재귀적 호출로 인한 스택의 공간 복잡도는 입니다.
class Trie:
def __init__(self):
self.root = {}
self.isEndOfWord = False
def insert(self, word):
node = self.root
for char in word:
if char not in node:
node[char] = {}
node = node[char]
node[self.isEndOfWord] = True
def search(self, word):
node = self.root
for char in word:
if char not in node:
return False
node = node[char]
return self.isEndOfWord in node
def startsWith(self, prefix):
node = self.root
for char in prefix:
if char not in node:
return False
node = node[char]
return True