Trie樹學習及python實現


一、定義

  Eg.一個保存了8個單詞的字典樹的結構如下圖所示,8個單詞分別是:“A”,“to”,“tea”,“ted”,“ten”,“i” ,“in”,“inn”。

 

 

 

  1. 字典(Trie)樹,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。
  2. 應用:統計和排序大量的字符串(但不僅限於字符串),經常被搜索引擎系統用於文本詞頻統計、前綴匹配用來搜索提示,也常用於計算左右信息熵、計算點互信息等,進階版可與默克爾樹融合成Merkle Patricia Tree用於區塊鏈。
  3. 優點:最大限度地減少無謂的字符串比較,利用字符串的公共前綴來降低查詢時間,空間換時間,因而查詢效率高。
  4. 缺點:如果大量字符串沒有共同前綴時很耗內存。 
  5. 性質
    1.   根節點不包含字符,除根節點外每一個節點都只包含一個字符。
    2.   從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串。
    3.   每個節點的所有子節點包含的字符都不相同。
  6. 時間復雜度:創建時間復雜度為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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM