package ds // ds-trie.go // 前缀树 (Trie),支持任意字符串的插入/查找/前缀匹配/删除。 // 每个节点 26 个子指针(默认按小写字母 a-z 索引),isEnd 标记单词结尾。 // 如需支持更多字符集,可改用 map[rune]*TrieNode。 // TrieNode 前缀树节点。 type TrieNode struct { children [26]*TrieNode isEnd bool } // Trie 前缀树。默认只支持小写字母 a-z。 type Trie struct { root *TrieNode } // NewTrie 创建空 Trie。 func NewTrie() *Trie { return &Trie{root: &TrieNode{}} } // Insert 插入单词。仅支持 a-z,其他字符会 panic(用 rune 索引前先校验)。 func (t *Trie) Insert(word string) { node := t.root for _, ch := range word { idx := ch - 'a' if idx < 0 || idx >= 26 { panic("trie: 只支持小写字母 a-z,收到非法字符") } if node.children[idx] == nil { node.children[idx] = &TrieNode{} } node = node.children[idx] } node.isEnd = true } // Search 查找完整单词是否存在(必须 isEnd)。 func (t *Trie) Search(word string) bool { node := t.find(word) return node != nil && node.isEnd } // StartsWith 是否存在以 prefix 为前缀的单词。 func (t *Trie) StartsWith(prefix string) bool { return t.find(prefix) != nil } // find 沿前缀走到对应节点,不存在返回 nil。 func (t *Trie) find(s string) *TrieNode { node := t.root for _, ch := range s { idx := ch - 'a' if idx < 0 || idx >= 26 { return nil } node = node.children[idx] if node == nil { return nil } } return node } // Delete 删除单词。仅当单词存在时删除,并回收不再被任何单词共享的节点。 // 返回是否删除成功(单词存在且被删除)。 func (t *Trie) Delete(word string) bool { // 先确认单词存在,避免与回收语义混淆。 node := t.find(word) if node == nil || !node.isEnd { return false } t.deleteNode(t.root, word, 0) return true } // deleteNode 递归回收:删除 word 沿途不再被需要的节点。 // 调用前需保证 word 存在。返回值:当前节点是否可被父节点回收。 func (t *Trie) deleteNode(node *TrieNode, word string, depth int) bool { if node == nil { return false } if depth == len(word) { node.isEnd = false return isEmpty(node) } idx := int(word[depth] - 'a') if idx < 0 || idx >= 26 { return false } child := node.children[idx] if t.deleteNode(child, word, depth+1) { node.children[idx] = nil } // 当前节点可回收:无子节点 且 非单词结尾 return isEmpty(node) && !node.isEnd } // isEmpty 节点是否无任何子节点。 func isEmpty(node *TrieNode) bool { for i := 0; i < 26; i++ { if node.children[i] != nil { return false } } return true }