ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

字典树(Trie树)

2021-09-14 09:06:56  阅读:187  来源: 互联网

标签:word Trie TrieNode prefix link return linki 字典


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

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

其实我们可以理解,每输入一个字符,应该整个搜索空间就会缩小,具体来说就是构造一颗如下的树,就是所谓的字典树(Trie树),一旦构建完成,搜索过程中,即可完成动态剪裁,最大搜索次数就是最长字符串长度。
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 &TrieNode{c, make(map[byte]*TrieNode), false}
}

type Trie struct {
	root *TrieNode
}

/** Initialize your data structure here. */
func Constructor() Trie {
	return Trie{getTrieNode('/')}
}

/** 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

原创,转载请注明来自

标签:word,Trie,TrieNode,prefix,link,return,linki,字典
来源: https://blog.csdn.net/wenzhou1219/article/details/120279961

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有