字典树(Trie树)

Posted 文大侠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字典树(Trie树)相关的知识,希望对你有一定的参考价值。

字典树主要是为了解决前缀匹配问题,比如下图的搜索输入前缀后匹配

比如有6个字符串how,hi,her,hello,so,see,如果现在输入字符判断是否要前缀匹配指定字符串,必须一个字符串比较,如果有100万就要比对100万次,如何优化这个问题呢?

其实我们可以理解,每输入一个字符,应该整个搜索空间就会缩小,具体来说就是构造一颗如下的树,就是所谓的字典树(Trie树),一旦构建完成,搜索过程中,即可完成动态剪裁,最大搜索次数就是最长字符串长度。

具体实现来说就是一颗多叉树,可以使用hashmap或者数组保存子节点指针。

如下是最常见的实现,对于确定的26个字母构成单词的trie树,可通过下标转换快速定位

public class Trie 
    public class TrieNode 
        public char data;
        public TrieNode[] link = new TrieNode[26];// 多叉树存储所有下级子节点(26个字母)
        public boolean isEndingChar = false; // 标记当前点是否为字符串结束点,判断是否完全匹配

        public TrieNode(char c) 
            this.data = c;
        
    

    TrieNode root;

    /**
     * Initialize your data structure here.
     */
    public Trie() 
        root = new TrieNode('/');
    

    /**
     * Inserts a word into the trie.
     */
    public void insert(String word) 
        TrieNode p = root;

        for (int i = 0; i < word.length(); i++) 
            int linki = word.charAt(i) - 'a';
            if (p.link[linki] == null) // 不存在
                p.link[linki] = new TrieNode(word.charAt(i));
            

            p = p.link[linki];
        

        p.isEndingChar = true;
    

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

        for (int i = 0; i < word.length(); i++) 
            int linki = word.charAt(i) - 'a';
            if (p.link[linki] == null) // 不存在
                return false;
            

            p = p.link[linki];
        

        return p.isEndingChar; // true 完全匹配/ false 前缀匹配,不是完全匹配
    

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

        for (int i = 0; i < prefix.length(); i++) 
            int linki = prefix.charAt(i) - 'a';
            if (p.link[linki] == null) // 不存在
                return false;
            

            p = p.link[linki];
        

        return true;
    

    /**
     * 获取指定前缀匹配的列表
     *
     * @param prefix
     * @return
     */
    public String[] getPrefixList(String prefix) 
        ArrayList<String> ret = new ArrayList<>();

        TrieNode p = root;

        for (int i = 0; i < prefix.length(); i++) 
            int linki = prefix.charAt(i) - 'a';
            if (p.link[linki] == null) // 不存在
                return ret.toArray(new String[]);
            

            p = p.link[linki];
        

        getFullStringFromNode(ret, prefix, p);
        return ret.toArray(new String[]);
    

    public void getFullStringFromNode(ArrayList<String> ret, String prefix, TrieNode p) 
        if (p.isEndingChar) 
            ret.add(prefix);
        

        for (int i = 0; i < p.link.length; i++) 
            if (p.link[i] != null) 
                getFullStringFromNode(ret, prefix + p.link[i].data, p.link[i]);
            
        
    


这里有很多优化空间,比如不是26个字母时如何处理,通常采用hashmap/treemap/skiplist都可以来优化空点带来的额外开销,比如使用hashmap优化这个过程,思想都差不多

type TrieNode struct 
	data         byte
	link         map[byte]*TrieNode // 多叉树存储所有下级子节点(26个字母)
	isEndingChar bool               // 标记当前点是否为字符串结束点,判断是否完全匹配


func getTrieNode(c byte) *TrieNode 
	return &TrieNodec, make(map[byte]*TrieNode), false


type Trie struct 
	root *TrieNode


/** Initialize your data structure here. */
func Constructor() Trie 
	return TriegetTrieNode('/')


/** Inserts a word into the trie. */
func (this *Trie) Insert(word string) 
	p := this.root

	for _, v := range []byte(word) 
		if _, ok := p.link[v]; !ok  // 不存在
			p.link[v] = getTrieNode(v)
		

		p = p.link[v]
	

	p.isEndingChar = true


/** Returns if the word is in the trie. */
func (this *Trie) Search(word string) bool 
	p := this.root

	for _, v := range []byte(word)  // 不存在
		if _, ok := p.link[v]; !ok 
			return false
		

		p = p.link[v]
	

	return p.isEndingChar


/** Returns if there is any word in the trie that starts with the given prefix. */
func (this *Trie) StartsWith(prefix string) bool 
	p := this.root

	for _, v := range []byte(prefix) 
		if _, ok := p.link[v]; !ok  // 不存在
			return false
		

		p = p.link[v]
	

	return true


func (this *Trie) GetPrefixList(prefix string) []string 
	ret := make([]string, 0)

	p := this.root

	for _, v := range []byte(prefix) 
		if _, ok := p.link[v]; !ok  // 不存在
			return ret
		

		p = p.link[v]
	

	// 匹配前缀后遍历输出全部匹配
	var getFullStringFromNode func(prefix string, p *TrieNode)
	getFullStringFromNode = func(prefix string, p *TrieNode) 
		if p.isEndingChar 
			ret = append(ret, prefix)
		

		for _, v := range p.link 
			getFullStringFromNode(fmt.Sprintf("%s%c", prefix, v.data), v)
		
	

	getFullStringFromNode(prefix, p)
	return ret

  • 源代码位置 https://github.com/JimWen/go-algo/tree/main/trie

原创,转载请注明来自

以上是关于字典树(Trie树)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构~trie树(字典树)

Trie树(字典树)

208. 实现 Trie (前缀树)-字典树

Trie树(字典树)整理

Trie树(字典树)

trie树(字典树)