1 字典树的定义
字典树也称作前缀树或者Trie树,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。(引自百度百科《字典树》)
2 字典树的特点
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
- 每个节点的所有子节点包含的字符都不相同。
3 字典树数据结构的实现方法
一个字典树要实现以下两种基本功能:
- 插入单词
- 搜索单词
- 搜索前缀
class Trie:
def __init__(self):
self.child = {}
def insert(self, word):
a = self.child
for i in word:
if not i in a.keys():
a[i] = {}
a = a[i]
a["end"] = True
def search(self, word):
a = self.child
for i in word:
if not i in a.keys():
return False
a = a[i]
return True if "end" in a.keys() else False
def startsWith(self, prefix):
a = self.child
for i in prefix:
if not i in a.keys():
return False
a = a[i]
return True
4 字典树的应用
问题1:
设计一个支持以下两种操作的数据结构:
void addWord(word)
bool search(word)
search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。 . 可以表示任何一个字母。
分析:
首先,该问题要求我们实现俩种功能,第一种功能就是一个基本的插入单词的功能,主要的问题其实集中在第二个功能。
那么第二个功能要求我们支持正则表达式的查询,可以用‘.’来代替任何一个字符。
在我们构建了字典树之后,在查询过程中,遇到了‘.’,那么我们要搜索该结点下的所有的子树,自然而然的,我们就能够想到要结合回溯法来解决这个问题。
代码实现:
class WordDictionary:
def __init__(self):
self.child = {}
def addWord(self, word: str) -> None:
temp = self.child
for char in word:
if char not in temp.keys():
temp[char] = {}
temp = temp[char]
temp['end'] = True
def search(self, word: str) -> bool:
def dfs(temp, index):
if index < len(word):
if len(temp.keys()) == 0 or (len(temp.keys())==1 and 'end' in temp.keys()):
return False
if index == len(word):
return True if 'end' in temp.keys() else False
if word[index] != '.':
if word[index] not in temp.keys():
return False
elif dfs(temp[word[index]], index + 1):
return True
else:
for key in temp.keys():
if key != 'end':
if dfs(temp[key], index + 1):
return True
return False
return dfs(self.child, 0)
问题2:
在英语中,我们有一个叫做词根(root)的概念,它可以跟着其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。
现在,给定一个由许多词根组成的词典和一个句子。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。
你需要输出替换之后的句子。
分析:
先根据词根,构造一个字典树。
然后将句子中的每个单词,都在字典树中搜索,若找到了匹配的前缀,则将单词替换成相应的词根即可。
代码实现:
class Solution:
def replaceWords(self, dict: list, sentence: str) -> str:
Trie = {}
for root in dict:
self.insert(Trie, root)
result = []
for word in sentence.split(" "):
result.append(self.search_root(Trie, word))
return " ".join(result) if result != [] else ""
def insert(self, Trie, root):
temp = Trie
for char in root:
if char not in temp.keys():
temp[char] = {}
temp = temp[char]
temp['end'] = True
def search_root(self, Trie, word):
temp = Trie
for index,char in enumerate(word):
if char not in temp.keys():
return word
temp = temp[char]
if 'end' in temp.keys():
return word[:index+1]
return word
问题3:
实现一个带有buildDict, 以及 search方法的魔法字典。
对于buildDict方法,你将被给定一串不重复的单词来构建一个字典。
对于search方法,你将被给定一个单词,并且判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
分析:
主要的问题还是在search功能这块,问题要求是将单词中的一个字母替换为任意一个字母,然后替换后的单词,仍然存在于字典中。
首先,题目要求的替换次数是一次,且必定要进行一次替换。
那么,如果在字典树的某个结点中搜索时,我们要替换当前的字符c,有下列几种情况:
如果在该结点之前,我们已经进行过替换了,那么不能够再次替换。
如果该结点除了含有c字符的子树外,不存在任何别的结点,那么不可替换。
如果c并不是该结点的子树,且结点子树不为空,那么可以进行替换。
如果c是该结点的子树,并且该结点除了c子树,还有其他子树,那么可以进行替换。
使用回溯法来解决这个问题,并且用一个变量来记录是否进行过替换。
代码实现:
class MagicDictionary:
def __init__(self):
self.child = {}
def buildDict(self, dict: list) -> None:
for word in dict:
temp = self.child
for char in word:
if char not in temp.keys():
temp[char] = {}
temp = temp[char]
temp['end'] = True
def search(self, word: str) -> bool:
def dfs(isModify, trie, index):
if index == len(word):
return True if (isModify and 'end' in trie.keys()) else False
if word[index] in trie.keys():
# not modify
if dfs(isModify, trie[word[index]], index+1):
return True
# modify
if not isModify:
for key in trie.keys():
if key != word[index] and key != 'end':
if dfs(True, trie[key], index+1):
return True
else:
if isModify == True:
return False
for key in trie.keys():
if key != word[index] and key != 'end':
if dfs(True, trie[key], index+1):
return True
return False
return dfs(False, self.child, 0)