算法: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 树精华全在代码中,多多看看上面给出的代码
本人写的也不一定很好,如果有不足之处,请在下面评论,我看到后会一一改正
如果有不懂的也可以在下方评论,大家相互讨论学习 ^_^

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