[從頭學數學] 第255節 Python實現資料結構:字典樹(Trie)
阿新 • • 發佈:2019-02-07
劇情提要:
阿偉看到了一本比較有趣的書,是關於《計算幾何》的,2008年由北清派出版。很好奇
它裡面講了些什麼,就來看看啦。
正劇開始:
星曆2016年08月03日 09:35:13, 銀河系厄爾斯星球中華帝國江南行省。
用例:
阿偉看到了一本比較有趣的書,是關於《計算幾何》的,2008年由北清派出版。很好奇
它裡面講了些什麼,就來看看啦。
正劇開始:
星曆2016年08月03日 09:35:13, 銀河系厄爾斯星球中華帝國江南行省。
[工程師阿偉]正在和[機器小偉]一起研究[計算幾何]]。
關於字典樹的實現,已經有非常多的博文涉及到了。但基本都是用內嵌陣列/列表實現的。
本博文是它的遞迴實現。
### # @usage 字典樹 # @author mw # @date 2016年08月02日 星期二 08:56:34 # @param # @return # ### class Trie: class TrieNode: def __init__(self,item,next = None, follows = None): self.item = item self.next = next self.follows = follows def __str__(self): return str(self.item); def getnext(self): return self.next; def setnext(self, next): self.next = next; def getfollows(self): return self.follows; def setfollows(self, follows): self.follows = follows; def getitem(self): return self.item; def setitem(self, item): self.item = item; def __iter__(self): yield self; if (self.follows != None): for x in self.follows: yield x; def iternext(self): yield self; if (self.next != None): for x in self.next.iternext(): yield x; #從兩個方向觀察節點資訊,一是從它的後續看,這是看一個單詞 #另一個是從它的兄弟看,這是在這個點的各分支 def info(self, direction = 0): s = ''; if direction == 0: for x in self: s += str(x)+'-->'; else: for x in self.iternext(): s += str(x)+'-->'; print(s); def __init__(self): self.start = Trie.TrieNode('^', None, None); def insert(self,item): self.start = Trie.__insert(self.start,item) def __contains__(self,item): return Trie.__contains(self.start,item) #生成詡根所在的結點 def __genNode(item): if (len(item) == 1): return Trie.TrieNode(item[0], None, None); elif (len(item) > 1): return Trie.TrieNode(item[0], None, Trie.__genNode(item[1:])); else: return None; def __insert(node,item): # This is the recursive insert function. if (item == None or item == ''): return None; elif (node == None): return Trie.__genNode(str(item)); elif (node.item == item[0]): node.setfollows(Trie.__insert(node.getfollows(), item[1:])); else: node.setnext(Trie.__insert(node.getnext(), item)); return node; def __contains(node, item): # This is the recursive membership test. #單詞結尾用'$'分隔,當然,如果用其它分隔符,此處必須更改item+'$' if Trie.__getNode(node, item) != None: return True; else: return False; #一般都是從字典的根結點開始遍歷才有意義 def getNode(self, item): return Trie.__getNode(self.start, item); #找到某一節點 def __getNode(node, item): if item == None or item == '': return None; elif node == None: return None; elif node.item != item[0]: return Trie.__getNode(node.next, item); else: if (len(item) > 1): return Trie.__getNode(node.follows, item[1:]); else: return node; #取某一結點的所有子結點 def getChild(self, item): node = self.getNode(item); child = []; if (node != None): if (node.follows != None): child.append(node.follows); p = node.follows; while (p.next != None): child.append(p.next); p = p.next; return child; #取某一結點的兄弟結點 def getBrother(self, item): if item not in self: return []; else: if (len(item) <= 1): root = self.start; brothers = []; for x in root.iternext(): brothers.append(x); return brothers; else: return self.getChild(item[:-1]); #取得某一結點的前一結點 def getPrev(self, item): if item not in self: return None; len_ = len(item); if (len_ <= 0): return None; elif (len_ == 1): root = self.start; for x in root.iternext(): if x.next.item == item: return x; return None; else: root = self.getNode(item[:-1]); root = root.follows; if (root.item == item[-1]): return None; else: for x in root.iternext(): if x.next.item == item[-1]: return x; return None; #取得父結點 def getParent(self, item): if item not in self: return None; len_ = len(item); if (len_ <= 1): return None; else: return self.getNode(item[:-1]); #判斷是否第一個孩子 def isFirstChild(self, item): if item not in self: return False; len_ = len(item); if (len_ <= 1): return False; else: parent = self.getNode(item[:-1]); if parent.follows.item == item[-1]: return True; return False; #從字典樹裡刪除某一單詞 def delete(self, item): node = self.getNode(item); #詞不存在或只是部分,就不操作 if node == None or node.follows != None: return []; len_ = len(item); if (len_ < 1): #一般這是不可能的,但有時詞典中也有空詞的位置 return []; else: for i in range(len_, 0, -1): s = item[0:i] brothers = self.getBrother(s); count = len(brothers); print(s); print(count); if (count > 1): break; if (len(s) <= 1): if count <= 2: #樹根要佔去一個位置,所以如果根的兄弟不多於兩個,這棵樹就只有一個詞 #刪掉後就只剩下一個樹根了 self.start.setnext(None); else: prev = self.getPrev(s); prev.setnext(prev.next.next); else: if (self.isFirstChild(s)): parent = self.getParent(s); parent.setfollows(parent.follows.next); else: prev = self.getPrev(s); prev.setnext(prev.next.next); print('在節點{0}處刪除'.format(s)); return item; #遍歷檢視字典 def dict(self): #單詞結束的末尾標記 endChar = '$'; count = 0; if (self.start != None): cursor = self.start; #具有後續節點的詞段 dict_1 = []; #最終版 dict_2 = []; while cursor != None: #if (cursor.follows == None): if (cursor.follows == None): #過濾掉詞尾結束標記 dict_2.append(str(cursor.item)[:-1]); else: dict_1.append(str(cursor.item)); cursor = cursor.next; while (len(dict_1) > 0): a = dict_1.pop(0); cursor = Trie.__getNode(self.start, a); count+=1; if (cursor != None): cursor = cursor.follows; while cursor != None: if (cursor.follows == None): dict_2.append((a+str(cursor.item))[:-1]); else: dict_1.append(a+str(cursor.item)); cursor = cursor.next; print('找結點{0}次'.format(count)); print('字典:', dict_2); else: print('字典為空'); #從某個結點開始遍歷,以這個結點為根的子樹所有成員 def dictFromItem(self, item): #單詞結束的末尾標記 endChar = '$'; count = 0; if (item == '^'): return self.dict(); root = self.getNode(item); if root == None: return []; else: #具有後續節點的詞段 dict_1 = []; #最終版 dict_2 = []; dict_1.append(item); while (len(dict_1) > 0): a = dict_1.pop(0); cursor = Trie.__getNode(self.start, a); count+=1; if (cursor != None): cursor = cursor.follows; while cursor != None: if (cursor.follows == None): dict_2.append((a+str(cursor.item))[:-1]); else: dict_1.append(a+str(cursor.item)); cursor = cursor.next; return dict_2;
用例:
</pre><p><span style="font-size:18px"></span><pre name="code" class="python">def main(): #計時開始 startTime = time.clock(); t = Trie(); a = ['I', 'love', 'lot', 'lance', 'you', 'you', 'love', 'me', 'i', 'have', 'a', 'book', 'you', 'have', 'a', 'pencil', 'pen', 'paper', 'lottol', 'banana']; #插入單詞,詞尾加結束符 for i in range(len(a)): t.insert(a[i]+'$'); #測試結點資訊 t.start.next.next.follows.next.info(); t.start.info(1); n1 = t.getNode('lottol'); if (n1 != None): n1.info(); n1.info(1); else: print('無該節點'); t.dict(); ''' #測試存在 print('banana'+'$' in t); print('how'+'$' in t); #測試關聯 print(t.getChild('lo')); print(t.isFirstChild('lo')); print(t.getPrev('la')); print(t.getParent('banana')); print(t.getPrev('i')); #測試兄弟 brothers = t.getBrother('i'); for i in range(len(brothers)): print(brothers[i], end = ', '); ''' #測試刪除 print(t.delete('have'+'$')); t.dict(); print(t.delete('pen'+'$')); t.dict(); t.insert('pen'+'$'); t.dict();
短短數行程式碼,寫得非常頭痛。用遞迴是不是比用列表有更大好處,阿偉也不清楚。
但是有一點可以肯定,用遞迴可以把整個字典連成一棵樹,而列表做不到。
即將奧運會了,阿偉決定好好地去研究一下奧運會,另外,最近也出了不少好看的電視劇,
也要抽點時間去看看。
本節到此結束,欲知後事如何,請看下回分解。