1. 程式人生 > >Implement Trie (Prefix Tree)

Implement Trie (Prefix Tree)

關注Trie 這種結構已經很久,Trie有一個很有趣的用途,那就是自動提示。而且,前不久在一次面試裡,也需要用Trie來解答。所以,在此對這個資料結構進行總結。
Trie,又稱單詞查詢樹或鍵樹,是一種樹形結構。典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。
它有3個基本性質:

  1. 根節點不包含字元,除根節點外每一個節點都只包含一個字元。
  2. 從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
  3. 每個節點的所有子節點包含的字元都不相同。

下面這個圖就是Trie的表示,每一條邊表示一個字元,如果結束,就用星號表示。在這個Trie結構裡,我們有下面字串,比如do, dork, dorm等,但是Trie裡沒有ba, 也沒有sen,因為在a, 和n結尾,沒有結束符號(星號)。

有了這樣一種資料結構,我們可以用它來儲存一個字典,要查詢改字典裡是否有相應的詞,是否非常的方便呢?我們也可以做智慧提示,我們把使用者已經搜尋的詞存在Trie裡,每當使用者輸入一個詞的時候,我們可以自動提示,比如當用戶輸入 ba, 我們會自動提示 bat 和 baii.

現在來討論Trie的實現。

首先,我們定義一個TrieNode。

 1 class TrieNode {
 2     // Initialize your data structure here.
 3     char content; // the character in the node
 4     boolean isEnd; //
whether the end of the words 5 int count; // the number of words sharing this character 6 LinkedList<TrieNode> childList; // the child list 7 8 public TrieNode(char c) { 9 childList = new LinkedList<TrieNode>(); 10 isEnd = false; 11 content = c;
12 count = 0; 13 } 14 15 public TrieNode subNode(char c) { 16 if (childList != null) { 17 for (TrieNode eachChild : childList) { 18 if (eachChild.content == c) { 19 return eachChild; 20 } 21 } 22 } 23 return null; 24 } 25 26 public TrieNode() { 27 childList = new LinkedList<TrieNode>(); 28 isEnd = false; 29 content = ' '; 30 count = 0; 31 } 32 }

現在我們來看這個Trie類的具體實現。

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        if (search(word) == true) return;

        TrieNode current = root;
        for (int i = 0; i < word.length(); i++) {
            TrieNode child = current.subNode(word.charAt(i));
            if (child != null) {
                current = child;
            } else {
                current.childList.add(new TrieNode(word.charAt(i)));
                current = current.subNode(word.charAt(i)); 
            }
            current.count++;
        }
        // Set isEnd to indicate end of the word
        current.isEnd = true;
    }

    // Returns if the word is in the trie.
    public boolean search(String word) {
        TrieNode current = root;

        for (int i = 0; i < word.length(); i++) {
            if (current.subNode(word.charAt(i)) == null)
                return false;
            else
                current = current.subNode(word.charAt(i)); // 非常巧妙
        }
        /*
         * This means that a string exists, but make sure its a word by checking
         * its 'isEnd' flag
         */
        if (current.isEnd == true) return true;
        return false;
    }

    // Returns if there is any word in the trie
    // that starts with the given prefix.
    public boolean startsWith(String word) {
        TrieNode current = root;

        for (int i = 0; i < word.length(); i++) {
            if (current.subNode(word.charAt(i)) == null)
                return false;
            else
                current = current.subNode(word.charAt(i));
        }
        return true;
    }
    
    public void deleteWord(String word){  
        if(search(word) == false) return;  
      
        TrieNode current = root;  
        for(char c : word.toCharArray()) {   
            TrieNode child = current.subNode(c);  
            if(child.count == 1) {  
                current.childList.remove(child);  
                return;  
            } else {  
                child.count--;  
                current = child;  
            }  
        }  
        current.isEnd = false;  
    }
}

時間複雜度分析:

 對於insert, 如果被插入的String長度是 k, 每對一個字元進行查詢,我們最多在child linkedlist裡面查詢26次(最多26個字母),所以,複雜度為O(26*k) = O(k). 對於 search, 複雜度是一樣的。

轉載請註明出處:http://blog.csdn.net/beiyeqingteng