日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Trie树进阶:Double-Array Trie原理及状态转移过程详解

發布時間:2025/3/20 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Trie树进阶:Double-Array Trie原理及状态转移过程详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:

? Trie樹本身就是一個很迷人的數據結構,何況是其改進的方案。

? 在本博客中我會從DAT(Double-Array Tire)的原理開始,并結合其源代碼對DAT的狀態轉移過程進行解析。如果因此你能從我的博客中有所收獲或啟發,It's my pleasure.


本文鏈接:http://blog.csdn.net/lemon_tree12138/article/details/49281865 -- Q-WHai?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?--轉載請注明出處


特別說明:

0.關于Trie樹的構建及使用,請移步:http://blog.csdn.net/lemon_tree12138/article/details/49177509

1.本文參考:

(0)雙數組Trie樹(DoubleArrayTrie)Java實現-碼農場

(1)基于雙數組Trie樹算法的字典改進和實現 - 期刊論文 - 道客巴巴


圖形展示及說明:

0.樸素Trie樹示意圖:

??

? 從上圖中可以看到,這樣的樹結構是非常稀疏的。造成了資源的巨大浪費。


1.DAT節點示意圖:

??

? 這里"NULL"代表結束。


DAT原理說明:

0.簡介:

? 在學習DAT(Double-Array Trie)之前,如果你對Tire樹的了解還是處在一個模糊的狀態,那么我想你現在可以移步到本人的另一篇博客《數據結構:字典樹的基本使用》,在對Trie樹有一個基本的了解之后,再來學習本文的內容應該會更加輕松自如(如果你對Trie樹已經有了或淺或深的了解,那么可以直接看下面的內容了)。

? DAT的本質是一個有限自動機(因為博主在學習DAT之前對自動機的相關內容也是一知半解,在學習DAT的過程,難免有一些痛苦。博主也緊追一下這方面的知識,也會在后面的博客中寫一些相關的博文).我們要構建一些狀態,用于狀態的自動轉移。顧名思義,在DAT中用的就是雙數組:base數組和check數組。雙數組的分工是:base負責記錄狀態,用于狀態轉移;check負責檢查各個字符串是否是從同一個狀態轉移而來,當check[i]為負值時,表示此狀態為字符串的結束。

? 你可能問一個這樣的問題:那么base數組和check數組是怎么來進行狀態轉移呢?

? 請看下面關于DAT雙數組的計算過程。


1.DAT中雙數組的計算過程:

假定有字符串狀態s,當前字符串狀態為t,假定t加了一個字符c就等于狀態tc,加了一個字符x等于狀態tx,那么有:
base[t] + c.code = base[tc]
base[t] + x.code = base[tx]
check[tc] = check[tx]

上面的幾個等式就是狀態base和它的轉移方程。


Double-Array Trie源碼解析:

0.特別說明:

? DAT中的節點信息如下:

private static class Node {int code;int depth;int left;int right; }? code: 代表節點字符的編碼。如:'a'.code = 97

? depth: 代表節點所在樹的深度。root.depth = 0

? left: 代表節點的子節點在字典中范圍的左邊界

? rigth:?代表節點的子節點在字典中范圍的右邊界


1.DAT的創建

? 和Trie樹一樣,DAT的創建只是創建Root的過程。如下:

public int build(List<String> _key) {key = _key;...resize(65536 * 32);...Node root_node = new Node();root_node.left = 0;root_node.right = keySize;root_node.depth = 0;...return error_;}


2.為節點parent生成子節點

? 在生成子節點的過程中,如果碰到parent='B',而'B'又是某一個key的結尾。該如何辦呢?

? 比如:比如若以"一舉"中的'舉'字符為parent,那么parent.depth = 2,"一舉".length = 2.

? 遇到這種情況,我們就需要對其進行過濾操作,過程如下:

String tmp = key.get(i);int currCode = 0;if (tmp.length() != parent.depth) {currCode = (int) tmp.charAt(parent.depth) + 1;}完整過程:

private int fetch(Node parent, List<Node> siblings) {...int prevCode = 0;for (int i = parent.left; i < parent.right; i++) {if (key.get(i).length() < parent.depth) {continue;}String tmp = key.get(i);int currCode = 0;if (tmp.length() != parent.depth) {currCode = (int) tmp.charAt(parent.depth) + 1;}...if (currCode != prevCode || siblings.size() == 0) {Node tmp_node = new Node();tmp_node.depth = parent.depth + 1;tmp_node.code = currCode;tmp_node.left = i;if (siblings.size() != 0) {siblings.get(siblings.size() - 1).right = i;}siblings.add(tmp_node);}prevCode = currCode;}if (siblings.size() != 0) {siblings.get(siblings.size() - 1).right = parent.right;}return siblings.size();}

3.向Trie樹中插入子節點

? 在DAT的創建過程中,insert是關鍵部分。

? 在insert操作里,我們使用了遞歸的思路來解決問題。為什么要利用遞歸呢?因為在我們狀態轉移的過程中,父節點的base值需要依賴子返回的begin值,因為在DAT中,code[null] = 0,所以也可以認為是依賴于子節點的check值,如此反復,層層嵌套。關于這一點在下面的結構圖展示中更容易體現。

(0)check的合法性檢查

? 之前我們說check數組是為了檢查各個字符串是否是從同一個狀態轉移而來,但是,要如何檢查呢?看下面這段代碼:

outer: while (true) {position++;if (check[position] != 0) {continue;} else if (first == 0) {...}begin = position - siblings.get(0).code; // 當前位置離第一個兄弟節點的距離...for (int i = 1; i < siblings.size(); i++) {if (check[begin + siblings.get(i).code] != 0) {continue outer;}}break;}? 這里的position即在數組中的下標。可以看到這是一個循環遍歷的過程,我們在一個合適的位置開始,逐步地嘗試check值是否合法,找到第一個合法的begin值即可。
? 而check[i]合法的條件就是check[i]是否為0。如果check[i]不為0,則說明此位置已經被別的狀態占領了,需要更換到下一個位置。

(1)計算所有子節點的check值

for (int i = 0; i < siblings.size(); i++) {check[begin + siblings.get(i).code] = begin;}

(2)計算所有子節點的base值

private int insert(List<Node> siblings) {...for (int i = 0; i < siblings.size(); i++) {List<Node> new_siblings = new ArrayList<Node>();if (fetch(siblings.get(i), new_siblings) == 0) {base[begin + siblings.get(i).code] = (value != null) ? (-value[siblings.get(i).left] - 1) : (-siblings.get(i).left - 1);...} else {int h = insert(new_siblings);base[begin + siblings.get(i).code] = h;}}return begin;}在這一步中,大家可以很明顯地看到,這是一個遞歸的過程。我們需要獲得子節點的begin值。


采用遞歸之后,我們的DAT節點的狀態轉移過程

(3)整體的insert過程:

private int insert(List<Node> siblings) {...// check的合法性檢查...// 計算所有子節點的check值// 計算所有子節點的base值...}

DAT中雙數組的狀態轉移過程


4.前綴查詢

? 現在假設待查找字符串T="走廊里的壁畫",我們需要在之前的字典中查找所有是T前綴的字符串。我們要怎么做呢?

? 其實在上面的雙數組狀態轉移過程圖中,我們可以很清楚地找到一條滿足條件的路徑.如下:

??

關鍵代碼如下:

public List<Integer> commonPrefixSearch(String key, int pos, int len, int nodePos) {...int b = base[nodePos];...for (int i = pos; i < len; i++) {p = b;n = base[p];if (b == check[p] && n < 0) {result.add(-n - 1);}p = b + (int) (keyChars[i]) + 1;if (b == check[p]) {b = base[p];} else {return result;}}p = b;n = base[p];if (b == check[p] && n < 0) {result.add(-n - 1);}return result;}

5.關鍵詞智能提示:

? 在上面“前綴查詢”的例子中,我們的匹配字符串中比較長,在還沒到字符串的最后一位就遇到狀態停止標志。而如果匹配字符串比較短,我就還可以做一些其他的事情了,比如常見的搜索引擎中關鍵詞智能提示。

? 過程就是在上一步的基礎上,把終止循環的條件修改為直到遇到一個狀態停步標志.這樣我們就可以在遍歷整條路徑。

? 這個功能,在源碼中沒有涉及。而本文的目的是在于解釋DAT的原理和其狀態轉移的過程。所以,這里就暫不貼代碼了。不過,在后期的《搜索引擎:對用戶輸入有誤的關鍵詞進行糾錯處理》博客中應該會有所涉及。感興趣的朋友,可以關注下。


實現源碼下載:

http://download.csdn.net/detail/u013761665/9201933

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的Trie树进阶:Double-Array Trie原理及状态转移过程详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。