算法:trie 樹(字典樹)

概念:
字典樹又稱單詞查找樹,Trie樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。(百度百科)

圖解:

上圖爲一顆字典樹,它表示字符串:aa、aaeb、aaec、ad、b、ba、cb、ca、cac
藍色表示一個字符串的結束標記節點

設計:
節點定義:
如何定義上面的字典樹中的一個節點呢?

class TrieNode {
    TrieNode[] children;
    boolean isEnd;
}

children 表示當前節點的孩子們節點引用,isEnd 表示當前節點是否爲結束標記節點
另外說一下,TrieNode[] children 孩子們節點引用是有指定大小的,假如我們存儲的字符串都是小寫字母,這個大小爲 26
每個不同的數組下標表示一個字符,例如:children[0] 表示小寫 a 的節點引用

代碼實現:

public class Trie {

    private TrieNode root;

    private class TrieNode {
        TrieNode[] children;
        boolean isEnd;
    }

    Trie() {
        root = new TrieNode();
        root.children = new TrieNode[26];
        root.isEnd = false;
    }

    /**
     * 將單詞插入到 trie 樹中
     * @param word 單詞
     */
    public void insert(String word) {
        int index = 0;
        TrieNode ptr = root;
        while (index < word.length()) {
            int position = word.charAt(index) - 'a';
            if (ptr.children[position] == null) {
                TrieNode node = new TrieNode();
                node.children = new TrieNode[26];
                node.isEnd = false;
                ptr.children[position] = node;
            }
            ptr = ptr.children[position];
            index++;
        }
        ptr.isEnd = true;
    }

    /**
     * 查找 trie 樹中是否存在該單詞
     * @param word 單詞
     * @return 存在返回 true,否則返回 false
     */
    public boolean search(String word) {
        int index = 0;
        TrieNode ptr = root;
        while (index < word.length()) {
            int position = word.charAt(index) - 'a';
            if (ptr.children[position] == null) {
                return false;
            }
            ptr = ptr.children[position];
            index++;
        }
        return ptr.isEnd;
    }

    /**
     * 查找 trie 樹中是否有給定前綴開頭的單詞
     * @param prefix 前綴
     * @return 存在返回 true,否則返回 false
     */
    public boolean startsWith(String prefix) {
        int index = 0;
        TrieNode ptr = root;
        while (index < prefix.length()) {
            int position = prefix.charAt(index) - 'a';
            if (ptr.children[position] == null) {
                return false;
            }
            ptr = ptr.children[position];
            index++;
        }
        return true;
    }

}

查找 trie 樹中所有單詞:

/**
 * 查找 trie 樹中所有單詞
 * @param trieNode 當前節點
 * @param wordList 單詞列表
 * @param word 臨時字母存儲區域
 */
public void getAllWord(TrieNode trieNode, List<String> wordList, StringBuilder word) {
	for (int i = 0; i < 26; i++) {
		if (trieNode.children[i] != null) {
			word.append((char) (i + 'a'));
			if (trieNode.children[i].isEnd) {
				wordList.add(word.toString());
			}
			getAllWord(trieNode.children[i], wordList, word);
			word.deleteCharAt(word.length() - 1);
		}
	}
}

查找 trie 樹中以給定前綴的單詞:

/**
 * 查找 trie 樹中以給定前綴的單詞
 * @param trieNode 當前節點
 * @param wordList 單詞列表
 * @param word 臨時字母存儲區域
 * @param prefix 給定前綴
 * @param index 前綴當前下標
 */
public void searchByPrefix(TrieNode trieNode, List<String> wordList, StringBuilder word, String prefix, int index) {
	if (index < prefix.length()) {
		char ch = prefix.charAt(index);
		int position = ch - 'a';
		if (trieNode.children[position] != null) {
			word.append((char) (position + 'a'));
			if (index == prefix.length() - 1 && trieNode.children[position].isEnd) {
				wordList.add(word.toString());
			}
			searchByPrefix(trieNode.children[position], wordList, word, prefix, index + 1);
			word.deleteCharAt(word.length() - 1);
		}
	} else {
		for (int i = 0; i < 26; i++) {
			if (trieNode.children[i] != null) {
				word.append((char) (i + 'a'));
				if (trieNode.children[i].isEnd) {
					wordList.add(word.toString());
				}
				searchByPrefix(trieNode.children[i], wordList, word, prefix, index + 1);
				word.deleteCharAt(word.length() - 1);
			}
		}
	}
}

測試:

public static void main(String[] args) {
	Trie trie = new Trie();
	trie.insert("aa");
	trie.insert("aaeb");
	trie.insert("aaec");
	trie.insert("ad");
	trie.insert("b");
	trie.insert("ba");
	trie.insert("cb");
	trie.insert("ca");
	trie.insert("cac");
	System.out.println("獲取 trie 樹中所有單詞:");
	LinkedList<String> wordList = new LinkedList<>();
	trie.getAllWord(trie.root, wordList, new StringBuilder());
	for (String word : wordList) {
		System.out.println(word);
	}
	System.out.println("獲取 trie 樹中給定前綴 aa 的單詞:");
	wordList = new LinkedList<>();
	trie.searchByPrefix(trie.root, wordList, new StringBuilder(), "aa", 0);
	for (String word : wordList) {
		System.out.println(word);
	}
}

總結:

trie 樹精華全在代碼中,多多看看上面給出的代碼
本人寫的也不一定很好,如果有不足之處,請在下面評論,我看到後會一一改正
如果有不懂的也可以在下方評論,大家相互討論學習 ^_^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章