一、定義
Eg.一個保存了8個單詞的字典樹的結構如下圖所示,8個單詞分別是:“A”,“to”,“tea”,“ted”,“ten”,“i” ,“in”,“inn”。
- 字典(Trie)樹,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。
- 應用:統計和排序大量的字符串(但不僅限於字符串),經常被搜索引擎系統用於文本詞頻統計、前綴匹配用來搜索提示,也常用於計算左右信息熵、計算點互信息等,進階版可與默克爾樹融合成Merkle Patricia Tree用於區塊鏈。
- 優點:最大限度地減少無謂的字符串比較,利用字符串的公共前綴來降低查詢時間,空間換時間,因而查詢效率高。
- 缺點:如果大量字符串沒有共同前綴時很耗內存。
- 性質
- 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
- 從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串。
- 每個節點的所有子節點包含的字符都不相同。
- 時間復雜度:創建時間復雜度為O(L),查詢時間復雜度是O(logL),查詢時間復雜度最壞情況下是O(L),L是字符串的長度。
二、基本操作(創建、插入和查找)
1 class TrieNode: 2 def __init__(self): 3 # 創建字典樹的節點 4 self.nodes = dict() 5 self.is_leaf = False 6 7 def insert(self, word: str): 8 """ 9 基本操作:插入 10 Parameters 11 ---------- 12 word : str 13 Returns 14 ------- 15 None. 16 17 """ 18 curr = self 19 for char in word: 20 if char not in curr.nodes: 21 curr.nodes[char] = TrieNode() 22 curr = curr.nodes[char] 23 curr.is_leaf = True 24 def search(self, word: str): 25 """ 26 基本操作:查找 27 Parameters 28 ---------- 29 word : str 30 31 Returns 32 ------- 33 34 """ 35 curr = self 36 for char in word: 37 if char not in curr.nodes: 38 return False 39 curr = curr.nodes[char] 40 return curr.is_leaf
三、應用實例
1 class TrieNode(object): 2 def __init__(self, value=None, count=0, parent=None): 3 # 值 4 self.value = value 5 # 頻數統計 6 self.count = count 7 # 父節點 8 self.parent = parent 9 # 子節點,{value: TrieNode} 10 self.children = {} 11 12 class Trie(object): 13 def __init__(self): 14 # 創建空的根節點 15 self.root = TrieNode() 16 17 def insert(self, sequence): 18 """ 19 基本操作:插入一個單詞序列 20 :param sequence:列表 21 :return: 22 """ 23 cur_node = self.root # 當前節點 24 for item in sequence: #遍歷單詞的每一個字符 25 if item not in cur_node.children: # 當前字符不在當前節點的子節點中 26 # 插入節點 27 child = TrieNode(value=item, count=1, parent=cur_node) # 新建節點 28 cur_node.children[item] = child # 將該節點接入當前節點的子節點 29 cur_node = child # 當前節點更新為子節點 30 else: 31 # 更新節點 32 cur_node = cur_node.children[item] # 更新當前節點為已存在的子節點 33 cur_node.count += 1 # 當前節點的計數+1 34 35 def search(self, sequence): 36 """ 37 基本操作,查詢是否存在完整序列 38 39 Parameters 40 ---------- 41 sequence : 列表 42 43 Returns: 44 45 """ 46 cur_node = self.root # 根節點 47 mark = True # 標記是否找到 48 for item in sequence: # 遍歷序列 49 if item not in cur_node.children: # 判斷子結點中是否存在 50 mark = False 51 break 52 else: # 存在 53 cur_node = cur_node.children[item] # 更新當前節點 54 # 如果還有子節點,說明序列並非完整,則查找不成功【此處可視情況找到前綴即反饋成功】 55 if cur_node.children: 56 mark = False 57 return mark 58 59 def delete(self, sequence): 60 """ 61 基本操作,刪除序列,准確來說是減少計數 62 63 Parameters 64 ---------- 65 sequence : 列表 66 67 Returns 68 69 """ 70 mark = False 71 if self.search(sequence): 72 mark = True 73 cur_node = self.root # 獲取當前節點 74 for item in sequence: # 遍歷序列 75 cur_node.children[item].count -= 1 # 節點計數減1 76 if cur_node.children[item].count == 0: # 如果節點計數為0 77 cur_node.children.pop(item) # 刪除該節點 78 break 79 else: 80 cur_node = cur_node.children[item] # 更新當前節點 81 return mark 82 83 def search_part(self, sequence, prefix, suffix, start_node=None): 84 """ 85 遞歸查找子序列,返回前綴和后綴節點(此處僅返回前后綴的內容與頻數) 86 87 Parameters 88 ---------- 89 sequence : 列表 90 DESCRIPTION. 91 prefix : TYPE 92 前綴字典,初始傳入空字典 93 suffix : TYPE 94 后綴字典,初始傳入空字典 95 start_node : TYPE, optional 96 起始節點,用於子樹的查詢 97 98 Returns 99 ------- 100 None. 101 102 """ 103 if start_node: # 該節點不為空,獲取第一個子結點 104 cur_node = start_node 105 prefix_node = start_node.parent 106 else: # 定位到根節點 107 cur_node = self.root 108 prefix_node = self.root 109 mark = True 110 # 必須從第一個結點開始對比 111 for i in range(len(sequence)): 112 if i == 0: 113 if sequence[i] != cur_node.value: # 第一個節點不相同,找到第一個相同值的節點 114 for child_node in cur_node.children.values(): # 該節點的每一個分支遞歸查找 115 self.search_part(sequence, prefix, suffix, child_node) 116 mark = False 117 break 118 else: 119 if sequence[i] not in cur_node.children: # 后序列中值不在當前節點孩子中 120 for child_node in cur_node.children.values(): 121 self.search_part(sequence, prefix, suffix, child_node) 122 mark = False 123 break 124 else: 125 cur_node = cur_node.children[sequence[i]] # 繼續查找序列 126 if mark: # 找到序列 127 if prefix_node.value in prefix: # 當前節點加入序列中 128 prefix[prefix_node.value] += cur_node.count 129 else: 130 prefix[prefix_node.value] = cur_node.count 131 for suffix_node in cur_node.children.values(): 132 if suffix_node.value in suffix: 133 suffix[suffix_node.value] += suffix_node.count 134 else: 135 suffix[suffix_node.value] = suffix_node.count 136 # 即使找到一部分還需繼續查找子節點 137 for child_node in cur_node.children.values(): 138 self.search_part(sequence, prefix, suffix, child_node) 139 140 if __name__ == "__main__": 141 trie = Trie() 142 texts = [["葬愛", "少年", "葬愛", "少年", "慕周力", "哈哈"], ["葬愛", "少年", "阿西吧"], ["烈", "烈", "風", "中"], ["忘記", "了", "愛"], 143 ["埋葬", "了", "愛"]] 144 145 for text in texts: 146 trie.insert(text) 147 markx = trie.search(["忘記", "了", "愛"]) 148 print(markx) 149 markx = trie.search(["忘記", "了"]) 150 print(markx) 151 markx = trie.search(["忘記", "愛"]) 152 print(markx) 153 markx = trie.delete(["葬愛", "少年", "王周力"]) 154 print(markx) 155 prefixx = {} 156 suffixx = {} 157 trie.search_part(["葬愛", "少年"], prefixx, suffixx) 158 print(prefixx) 159 print(suffixx)
參考文獻
[1]. 百度百科詞條【字典樹】:https://baike.baidu.com/item/字典樹/9825209
[2]. 看動畫輕松理解「Trie樹」,https://www.cxyxiaowu.com/1873.html
[3]. 字典樹python實現,https://blog.csdn.net/danengbinggan33/article/details/82151220
[4]. 吐血攻略Trie,包教包會,https://leetcode-cn.com/problems/short-encoding-of-words/solution/99-java-trie-tu-xie-gong-lue-bao-jiao-bao-hui-by-s/