Trie樹

一:概念

     下面我們有and,as,at,cn,com這些關鍵詞,那麼如何構建trie樹呢?

從上面的圖中,我們或多或少的可以發現一些好玩的特性。

      第一:根節點不包含字符,除根節點外的每一個子節點都包含一個字符。

      第二:從根節點到某一節點,路徑上經過的字符連接起來,就是該節點對應的字符串。

      第三:每個單詞的公共前綴作爲一個字符節點保存。

 

二:使用範圍

     既然學Trie樹,我們肯定要知道這玩意是用來幹嘛的。

     第一:詞頻統計。

            可能有人要說了,詞頻統計簡單啊,一個hash或者一個堆就可以打完收工,但問題來了,如果內存有限呢?還能這麼

             玩嗎?所以這裏我們就可以用trie樹來壓縮下空間,因爲公共前綴都是用一個節點保存的。

     第二: 前綴匹配

            就拿上面的圖來說吧,如果我想獲取所有以"a"開頭的字符串,從圖中可以很明顯的看到是:and,as,at,如果不用trie樹,

            你該怎麼做呢?很顯然樸素的做法時間複雜度爲O(N2) ,那麼用Trie樹就不一樣了,它可以做到h,h爲你檢索單詞的長度,

            可以說這是秒殺的效果。

舉個例子:現有一個編號爲1的字符串”and“,我們要插入到trie樹中,採用動態規劃的思想,將編號”1“計入到每個途徑的節點中,

              那麼以後我們要找”a“,”an“,”and"爲前綴的字符串的編號將會輕而易舉。

三:實際操作

     到現在爲止,我想大家已經對trie樹有了大概的掌握,下面我們看看如何來實現。

1:定義trie樹節點

     爲了方便,我也採用純英文字母,我們知道字母有26個,那麼我們構建的trie樹就是一個26叉樹,每個節點包含26個子節點。

 1 #region Trie樹節點
 2         /// <summary>
 3         /// Trie樹節點
 4         /// </summary>
 5         public class TrieNode
 6         {
 7             /// <summary>
 8             /// 26個字符,也就是26叉樹
 9             /// </summary>
10             public TrieNode[] childNodes;
11 
12             /// <summary>
13             /// 詞頻統計
14             /// </summary>
15             public int freq;
16 
17             /// <summary>
18             /// 記錄該節點的字符
19             /// </summary>
20             public char nodeChar;
21 
22             /// <summary>
23             /// 插入記錄時的編碼id
24             /// </summary>
25             public HashSet<int> hashSet = new HashSet<int>();
26 
27             /// <summary>
28             /// 初始化
29             /// </summary>
30             public TrieNode()
31             {
32                 childNodes = new TrieNode[26];
33                 freq = 0;
34             }
35         }

2: 添加操作

     既然是26叉樹,那麼當前節點的後續子節點是放在當前節點的哪一叉中,也就是放在childNodes中哪一個位置,這裏我們採用

      int k = word[0] - 'a'來計算位置。

 1         /// <summary>
 2         /// 插入操作
 3         /// </summary>
 4         /// <param name="root"></param>
 5         /// <param name="s"></param>
 6         public void AddTrieNode(ref TrieNode root, string word, int id)
 7         {
 8             if (word.Length == 0)
 9                 return;
10 
11             //求字符地址,方便將該字符放入到26叉樹中的哪一叉中
12             int k = word[0] - 'a';
13 
14             //如果該叉樹爲空,則初始化
15             if (root.childNodes[k] == null)
16             {
17                 root.childNodes[k] = new TrieNode();
18 
19                 //記錄下字符
20                 root.childNodes[k].nodeChar = word[0];
21             }
22 
23             //該id途徑的節點
24             root.childNodes[k].hashSet.Add(id);
25 
26             var nextWord = word.Substring(1);
27 
28             //說明是最後一個字符,統計該詞出現的次數
29             if (nextWord.Length == 0)
30                 root.childNodes[k].freq++;
31 
32             AddTrieNode(ref root.childNodes[k], nextWord, id);
33         }

3:刪除操作

     刪除操作中,我們不僅要刪除該節點的字符串編號,還要對詞頻減一操作。

  /// <summary>
        /// 刪除操作
        /// </summary>
        /// <param name="root"></param>
        /// <param name="newWord"></param>
        /// <param name="oldWord"></param>
        /// <param name="id"></param>
        public void DeleteTrieNode(ref TrieNode root, string word, int id)
        {
            if (word.Length == 0)
                return;

            //求字符地址,方便將該字符放入到26叉樹種的哪一顆樹中
            int k = word[0] - 'a';

            //如果該叉樹爲空,則說明沒有找到要刪除的點
            if (root.childNodes[k] == null)
                return;

            var nextWord = word.Substring(1);

            //如果是最後一個單詞,則減去詞頻
            if (word.Length == 0 && root.childNodes[k].freq > 0)
                root.childNodes[k].freq--;

            //刪除途經節點
            root.childNodes[k].hashSet.Remove(id);

            DeleteTrieNode(ref root.childNodes[k], nextWord, id);
        }


發佈了29 篇原創文章 · 獲贊 19 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章