字典树

定义

字典树,顾名思义,是关于“字典”的一棵树。即:它是对于字典的一种存储方式(所以是一种数据结构而不是算法)。这个词典中的每个“单词”就是从根节点出发一直到某一个目标节点的路径,路径中每条边的字母连起来就是一个单词。

结构实例

trie1
trie1

字典树的功能

根据字典树的概念,我们可以发现:字典树的本质是把很多字符串拆成单个字符的形式,以树的方式存储起来。所以我们说字典树维护的是”字典“。那么根据这个最基本的性质,我们可以由此延伸出字典树的很多妙用。简单总结起来大体如下:

  • 1、维护字符串集合(即字典)。
  • 2、向字符串集合中插入字符串(即建树)。
  • 3、查询字符串集合中是否有某个字符串(即查询)。
  • 4、统计字符串在集合中出现的个数(即统计)。
  • 5、将字符串集合按字典序排序(即字典序排序)。
  • 6、求集合内两个字符串的LCP(Longest Common Prefix,最长公共前缀)(即求最长公共前缀)。

我们可以发现,以上列举出的功能都是建立在“字符串集合”的基础上的。再一次强调,字典树是“字典”的树,一切功能都是“字典”的功能。这也为我们使用字典树的时候提供了一个准则:看到一大堆字符串同时出现,就往哈希和Trie树那边想一下。

基于字典树实现的域名匹配

有一个需求,给出一个域名名单,当输入一个新域名之后判断其是否存在域名名单中,要求支持通配符匹配

代码实现

根据字典树结构,使用go语言实现如下:

package main

import (
    "fmt"
    "github.com/gookit/goutil/arrutil"
    "github.com/gookit/goutil/maputil"
    "strings"
)

type TrieNode struct {
    children map[string]*TrieNode
    isEnd    bool
}

type Trie struct {
    root *TrieNode
}

func initTrie() *Trie {
    return &Trie{root: &TrieNode{children: make(map[string]*TrieNode)}}
}

func (t *Trie) insertDomain(domain string) {
    parts := strings.Split(domain, ".")
    arrutil.Reverse(parts)
    node := t.root

    for _, part := range parts {
        if node.children[part] == nil {
            node.children[part] = &TrieNode{children: make(map[string]*TrieNode)}
        }
        node = node.children[part]
    }

    node.isEnd = true
}

func (t *Trie) searchDomain(domain string) bool {
    parts := strings.Split(domain, ".")
    arrutil.Reverse(parts)
    node := t.root

    for _, part := range parts {
        isNil := node.children[part] == nil
        if isNil && !maputil.HasKey(node.children, "*") {
            return false
        }
        if isNil {
            node = node.children["*"]
        } else {
            node = node.children[part]
        }
    }

    return node != nil && node.isEnd
}

func main() {
    trie := initTrie()

    // 示例域名
    domains := []string{"wx.*.com", "example.com", "test.org"}

    // 插入域名到 Trie 中
    for _, domain := range domains {
        trie.insertDomain(domain)
    }

    // 搜索示例
    searchDomains := []string{"wx.qq.com", "openai.com", "test.org", "wx.qq1.com"}

    // 搜索域名是否存在于 Trie 中
    for _, domain := range searchDomains {
        if trie.searchDomain(domain) {
            fmt.Printf("%s 存在\n", domain)
        } else {
            fmt.Printf("%s 不存在\n", domain)
        }
    }
}

参考文档

字典树(Trie)详解 - Seaway-Fu - 博客园 (cnblogs.com)

字典树 (Trie) - OI Wiki (oi-wiki.org)