python實現字典樹
前言
trie 樹 也叫字典樹,也是一種 N 叉樹,是一種特殊的前綴樹結構。通常來說,一個前綴樹是用來存儲字符串的。前綴樹的每一個節點代表一個字符串(前綴)。每一個節點會有多個子節點,通往不同子節點的路徑上有着不同的字符。子節點代表的字符串是由節點本身的原始字符串,以及通往該子節點路徑上所有的字符組成的。
前綴樹的一個重要的特性是,節點所有的后代都與該節點相關的字符串有着共同的前綴。這就是前綴樹名稱的由來。
實現
1 # coding=utf-8 2 #字典嵌套牛逼,別人寫的,這樣每一層非常多的東西,搜索就快了,樹高26.所以整體搜索一個不關多大的單詞表,還是O(1). 3 4 ''' 5 Python 字典 setdefault() 函數和get() 方法類似, 如果鍵不存在於字典中,將會添加鍵並將值設為默認值。 6 說清楚就是:如果這個鍵存在字典中,那么這句話就不起作用,否則就添加字典里面這個key的取值為后面的默認值. 7 簡化了字典計數的代碼.並且這個函數的返回值是做完這些事情之后這個key的value值. 8 dict.setdefault(key, default=None) 9 Python 字典 get() 函數返回指定鍵的值,如果值不在字典中返回默認值。 10 dict.get(key, default=None) 11 ''' 12 13 14 class Trie: 15 root = {} 16 END = '/' # 加入這個是為了區分單詞和前綴,如果這一層node里面沒有/他就是前綴.不是我們要找的單詞. 17 18 def insert(self, word): 19 # 從根節點遍歷單詞,char by char,如果不存在則新增,最后加上一個單詞結束標志 20 node = self.root 21 for c in word: 22 """ 23 利用嵌套來做,一個trie樹的子樹也是一個trie樹. 24 利用setdefault的返回值是value的特性,如果找到了key就進入value 25 沒找到,就建立一個空字典 26 """ 27 node = node.setdefault(c, {}) 28 node[self.END] = None 29 # 當word都跑完了,就已經沒有字了.那么當前節點也就是最后一個字母的節點 30 # 加一個屬性標簽end.這個end里面隨意放一個value即可.因為我們只是判定end這個key是否在字典里面. 31 # 考慮insert 同一個單詞2次的情況,第二次insert 這個單詞的時候,因為用setdefault 32 # insert里面的話都不對原字典進行修改.正好是我們需要的效果. 33 # 這個self.END很重要,可以作為信息來存儲.比如里面可以輸入這個單詞的 34 # 起源,發音,拼寫,詞組等作為信息存進去.找這個單詞然后讀出單詞的信息. 35 36 def delete(self, word): # 字典中刪除word 37 node = self.root 38 for c in word: 39 if c not in node: 40 print('字典中沒有不用刪') 41 return False 42 node = node[c] 43 # 如果找到了就把'/'刪了 44 del node['/'] 45 # 后面還需要檢索一遍,找一下是否有前綴的后面沒有單詞的.把前綴的最后一個字母也去掉.因為沒單詞了,前綴也沒意義存在了. 46 # 也就是說最后一個字母這個節點,只有'/',刪完如果是空的就把這個節點也刪了. 47 while node == {}: 48 if word == '': 49 return 50 tmp = word[-1] 51 word = word[:-1] 52 node = self.root 53 for c in word: 54 node = node[c] 55 del node[tmp] 56 57 def search(self, word): 58 node = self.root 59 for c in word: 60 if c not in node: 61 return False 62 node = node[c] 63 return self.END in node 64 65 def associate_search(self, pre): # 搜索引擎里面的功能是你輸入東西,不關是不是單詞,他都輸出以這個東西為前綴的單詞. 66 node = self.root 67 for c in pre: 68 if c not in node: 69 return [] # 因為字典里面沒有pre這個前綴 70 node = node[c] # 有這個前綴就繼續走,這里有個問題就是需要記錄走過的路徑才行. 71 # 運行到這里node就是最后一個字母所表示的字典. 72 # 舉一個栗子:圖形就是{a,b,c}里面a的value是{b,c,d} d的value是{/,e,f} 那么/代表的單詞就是ad,看這個形象多了 73 # 首先看這個字母所在的字典有沒有END,返回a這個list 74 75 # 然后下面就是把前綴是pre的單詞都加到a里面. 76 # 應該用廣度遍歷,深度遍歷重復計算太多了.好像深度也很方便,並且空間開銷很小. 77 # 廣度不行,每一次存入node,沒用的信息存入太多了.需要的信息只是這些key是什么,而不需要存入node. 78 # 但是深度遍歷,又需要一個flag記錄每個字母.字典的key又實現不了. 79 # 用函數遞歸來遍歷:只能先用這個效率最慢的先寫了 80 # 因為你遍歷一直到底,到底一定是'/'和None.所以一定travel出來的是單詞不是中間結果. 81 def travel(node): # 返回node節點和他子節點拼出的所有單詞 82 if node == None: 83 return [''] 84 a = [] # 現在node是/ef 85 86 for i in node: 87 tmp = node[i] 88 tmp2 = travel(tmp) 89 for j in tmp2: 90 91 a.append(i + j) 92 return a 93 94 output = travel(node) 95 for i in range(len(output)): 96 output[i] = (pre + output[i])[:-1] 97 return output 98 99 100 101 a = Trie() 102 a.insert('apple') 103 a.insert('appl') 104 a.insert("badj") 105 print(a.root) 106 print(a.associate_search('ap')) 107 a.delete('apple') 108 print(a.search('apple'))