3-9 Trie(前缀树,26进制树)

Trie(前缀树 / Prefix Tree)

Trie(前缀树,Prefix Tree),又称字典树(Digital Tree),是一种专门用于处理字符串集合的树形数据结构。它的核心思想是:利用字符串的公共前缀来节省存储空间并加速查询

在 Trie 中,每个节点代表一个字符(Character),从根节点(Root)到某个节点的路径代表一个字符串前缀。如果一条从根到某节点的路径对应一个完整的单词,则该节点会被标记为单词结尾(End of Word)。对于只包含小写英文字母的 Trie,每个节点最多有 26 个子节点,因此它是一棵 26 叉树(26-ary Tree)。

下面是一棵包含 "cat", "car", "care", "dog", "do" 五个单词的 Trie:

            (root)
           /      \
          c        d
          |        |
          a        o [E]
         / \       |
        t   r [E]  g
       [E]  |     [E]
            e
           [E]

其中 [E] 表示该节点是一个单词的结尾。可以看到 "car""care" 共享前缀 "car""do""dog" 共享前缀 "do"——这正是 Trie 的优势所在。


节点定义

Trie 的节点需要两部分信息:指向子节点的指针数组,以及一个标记该节点是否为单词结尾的布尔值。

C++ 节点定义

struct TrieNode {
    TrieNode* children[26];  // 26 letters: a-z
    bool isEndOfWord;        // true if node represents end of a word

    TrieNode() {
        for (int i = 0; i < 26; i++)
            children[i] = nullptr;
        isEndOfWord = false;
    }
};

C++ 使用 struct 配合构造函数初始化,children 数组固定 26 个槽位对应 a-z,初始全部为 nullptr

C 节点定义

#include <stdbool.h>

typedef struct TrieNode {
    struct TrieNode* children[26];  // 26 letters: a-z
    bool isEndOfWord;               // true if node represents end of a word
} TrieNode;

// Create and initialize a new Trie node
TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++)
        node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

C 语言使用 typedef 定义结构体,并通过 malloc 动态分配内存。createNode 函数负责初始化所有字段。

Python 节点定义

class TrieNode:
    def __init__(self):
        self.children = {}     # dict: char -> TrieNode
        self.is_end_of_word = False

Python 版本使用字典(dict)存储子节点,键为字符,值为子节点引用。这种方式更加灵活,不需要预先分配固定大小的数组,且天然支持任意字符集。

Go 节点定义

type TrieNode struct {
    children [26]*TrieNode // 26 letters: a-z
    isEnd    bool          // true if node represents end of a word
}

Go 语言使用结构体 TrieNode 定义节点,children 是固定长度为 26 的数组,对应 a-z 共 26 个小写字母。Go 中数组会自动初始化为零值(指针为 nil,布尔值为 false),因此无需显式初始化。


插入操作

插入(Insert)的核心思路是:从根节点出发,逐个处理字符串中的字符。如果对应字符的子节点已存在则沿其继续,否则创建新节点。处理完最后一个字符后,将当前节点标记为单词结尾。

例如,依次插入 "cat", "car", "care", "dog", "do"

  1. 插入 "cat":创建 c -> a -> t 路径,标记 t 为单词结尾
  2. 插入 "car":复用 c -> a,从 a 创建新子节点 r,标记 r 为单词结尾
  3. 插入 "care":复用 c -> a -> r,从 r 创建新子节点 e,标记 e 为单词结尾
  4. 插入 "dog":创建 d -> o -> g 路径,标记 g 为单词结尾
  5. 插入 "do":复用 d -> o,标记 o 为单词结尾

C++ 插入实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;

    TrieNode() {
        for (int i = 0; i < 26; i++)
            children[i] = nullptr;
        isEndOfWord = false;
    }
};

void insert(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';           // map character to 0-25
        if (curr->children[index] == nullptr)
            curr->children[index] = new TrieNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;           // mark last node as end of word
}

int main() {
    TrieNode* root = new TrieNode();

    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    cout << "Words inserted: cat, car, care, dog, do" << endl;

    return 0;
}

C 插入实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++)
        node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void insert(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';      // map character to 0-25
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;           // mark last node as end of word
}

int main() {
    TrieNode* root = createNode();

    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    printf("Words inserted: cat, car, care, dog, do\n");

    return 0;
}

Python 插入实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

def insert(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            curr.children[ch] = TrieNode()
        curr = curr.children[ch]
    curr.is_end_of_word = True         # mark last node as end of word

if __name__ == '__main__':
    root = TrieNode()

    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    print("Words inserted: cat, car, care, dog, do")

运行该程序将输出:

Words inserted: cat, car, care, dog, do

Go 插入实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

func insert(root *TrieNode, word string) {
    curr := root
    for _, ch := range word {
        index := ch - 'a'              // map character to 0-25
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true                  // mark last node as end of word
}

func main() {
    root := &TrieNode{}

    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    fmt.Println("Words inserted: cat, car, care, dog, do")
}

运行该程序将输出:

Words inserted: cat, car, care, dog, do

插入操作的时间复杂度为 O(m),其中 m 为待插入字符串的长度。每次插入最多创建 m 个新节点。


搜索操作

搜索(Search)用于判断一个完整的单词是否存在于 Trie 中。从根节点出发,逐个字符向下查找。如果某个字符对应的子节点不存在,说明该单词不在 Trie 中;如果顺利遍历完所有字符,还需要检查最后到达的节点是否被标记为单词结尾。

C++ 搜索实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) children[i] = nullptr;
        isEndOfWord = false;
    }
};

void insert(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            curr->children[index] = new TrieNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// Search for a complete word in the Trie
bool search(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            return false;              // character path not found
        curr = curr->children[index];
    }
    return curr->isEndOfWord;          // true only if it's a complete word
}

int main() {
    TrieNode* root = new TrieNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    cout << "Search 'cat': " << (search(root, "cat") ? "Found" : "Not found") << endl;
    cout << "Search 'ca': "  << (search(root, "ca")  ? "Found" : "Not found") << endl;
    cout << "Search 'cow': " << (search(root, "cow") ? "Found" : "Not found") << endl;

    return 0;
}

C 搜索实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++) node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void insert(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// Search for a complete word in the Trie
bool search(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            return false;              // character path not found
        curr = curr->children[index];
    }
    return curr->isEndOfWord;          // true only if it's a complete word
}

int main() {
    TrieNode* root = createNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    printf("Search 'cat': %s\n", search(root, "cat") ? "Found" : "Not found");
    printf("Search 'ca': %s\n",  search(root, "ca")  ? "Found" : "Not found");
    printf("Search 'cow': %s\n", search(root, "cow") ? "Found" : "Not found");

    return 0;
}

Python 搜索实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

def insert(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            curr.children[ch] = TrieNode()
        curr = curr.children[ch]
    curr.is_end_of_word = True

# Search for a complete word in the Trie
def search(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            return False              # character path not found
        curr = curr.children[ch]
    return curr.is_end_of_word        # true only if it's a complete word

if __name__ == '__main__':
    root = TrieNode()
    for w in ["cat", "car", "care", "dog", "do"]:
        insert(root, w)

    print(f"Search 'cat': {'Found' if search(root, 'cat') else 'Not found'}")
    print(f"Search 'ca': {'Found' if search(root, 'ca') else 'Not found'}")
    print(f"Search 'cow': {'Found' if search(root, 'cow') else 'Not found'}")

运行该程序将输出:

Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found
  • "cat" 被找到,因为 Trie 中存在完整的 "cat" 路径且 t 节点被标记为单词结尾
  • "ca" 未找到,虽然路径 c -> a 存在,但 a 节点未被标记为单词结尾——"ca" 只是一个前缀,不是完整单词
  • "cow" 未找到,因为从节点 a 无法沿 'w' 继续向下

Go 搜索实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

func insert(root *TrieNode, word string) {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true
}

// Search for a complete word in the Trie
func search(root *TrieNode, word string) bool {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            return false            // character path not found
        }
        curr = curr.children[index]
    }
    return curr.isEnd               // true only if it's a complete word
}

func main() {
    root := &TrieNode{}
    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    fmt.Printf("Search 'cat': %s\n", foundStr(search(root, "cat")))
    fmt.Printf("Search 'ca': %s\n", foundStr(search(root, "ca")))
    fmt.Printf("Search 'cow': %s\n", foundStr(search(root, "cow")))
}

func foundStr(found bool) string {
    if found {
        return "Found"
    }
    return "Not found"
}

运行该程序将输出:

Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found

前缀搜索(StartsWith)

前缀搜索(Prefix Search)与普通搜索类似,但不要求到达的节点是单词结尾。只需判断 Trie 中是否存在以给定前缀开头的单词即可。这在自动补全(Autocomplete)等场景中非常有用。

C++ 前缀搜索实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) children[i] = nullptr;
        isEndOfWord = false;
    }
};

void insert(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            curr->children[index] = new TrieNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// Check if any word in the Trie starts with the given prefix
bool startsWith(TrieNode* root, const string& prefix) {
    TrieNode* curr = root;
    for (char ch : prefix) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            return false;              // prefix path not found
        curr = curr->children[index];
    }
    return true;                       // prefix exists, regardless of isEndOfWord
}

int main() {
    TrieNode* root = new TrieNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    cout << "StartsWith 'ca': "   << (startsWith(root, "ca")   ? "Yes" : "No") << endl;
    cout << "StartsWith 'do': "   << (startsWith(root, "do")   ? "Yes" : "No") << endl;
    cout << "StartsWith 'ba': "   << (startsWith(root, "ba")   ? "Yes" : "No") << endl;
    cout << "StartsWith 'catx': " << (startsWith(root, "catx") ? "Yes" : "No") << endl;

    return 0;
}

C 前缀搜索实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++) node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void insert(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// Check if any word in the Trie starts with the given prefix
bool startsWith(TrieNode* root, const char* prefix) {
    TrieNode* curr = root;
    for (int i = 0; prefix[i] != '\0'; i++) {
        int index = prefix[i] - 'a';
        if (curr->children[index] == NULL)
            return false;              // prefix path not found
        curr = curr->children[index];
    }
    return true;                       // prefix exists, regardless of isEndOfWord
}

int main() {
    TrieNode* root = createNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    printf("StartsWith 'ca': %s\n",   startsWith(root, "ca")   ? "Yes" : "No");
    printf("StartsWith 'do': %s\n",   startsWith(root, "do")   ? "Yes" : "No");
    printf("StartsWith 'ba': %s\n",   startsWith(root, "ba")   ? "Yes" : "No");
    printf("StartsWith 'catx': %s\n", startsWith(root, "catx") ? "Yes" : "No");

    return 0;
}

Python 前缀搜索实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

def insert(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            curr.children[ch] = TrieNode()
        curr = curr.children[ch]
    curr.is_end_of_word = True

# Check if any word in the Trie starts with the given prefix
def starts_with(root, prefix):
    curr = root
    for ch in prefix:
        if ch not in curr.children:
            return False              # prefix path not found
        curr = curr.children[ch]
    return True                       # prefix exists, regardless of is_end_of_word

if __name__ == '__main__':
    root = TrieNode()
    for w in ["cat", "car", "care", "dog", "do"]:
        insert(root, w)

    print(f"StartsWith 'ca': {'Yes' if starts_with(root, 'ca') else 'No'}")
    print(f"StartsWith 'do': {'Yes' if starts_with(root, 'do') else 'No'}")
    print(f"StartsWith 'ba': {'Yes' if starts_with(root, 'ba') else 'No'}")
    print(f"StartsWith 'catx': {'Yes' if starts_with(root, 'catx') else 'No'}")

运行该程序将输出:

StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No
StartsWith 'catx': No
  • "ca" 返回 Yes,因为 Trie 中有 "cat", "car", "care" 都以 "ca" 开头
  • "do" 返回 Yes,因为 "dog""do" 都以 "do" 开头
  • "ba" 返回 No,因为 Trie 中没有任何单词以 "ba" 开头
  • "catx" 返回 No,因为沿着 c -> a -> t 继续后,不存在 'x' 子节点

Go 前缀搜索实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

func insert(root *TrieNode, word string) {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true
}

// Check if any word in the Trie starts with the given prefix
func startsWith(root *TrieNode, prefix string) bool {
    curr := root
    for _, ch := range prefix {
        index := ch - 'a'
        if curr.children[index] == nil {
            return false            // prefix path not found
        }
        curr = curr.children[index]
    }
    return true                     // prefix exists, regardless of isEnd
}

func main() {
    root := &TrieNode{}
    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    fmt.Printf("StartsWith 'ca': %s\n", yesNo(startsWith(root, "ca")))
    fmt.Printf("StartsWith 'do': %s\n", yesNo(startsWith(root, "do")))
    fmt.Printf("StartsWith 'ba': %s\n", yesNo(startsWith(root, "ba")))
    fmt.Printf("StartsWith 'catx': %s\n", yesNo(startsWith(root, "catx")))
}

func yesNo(val bool) string {
    if val {
        return "Yes"
    }
    return "No"
}

运行该程序将输出:

StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No
StartsWith 'catx': No

删除操作

删除(Delete)是最复杂的操作。核心原则是:只删除不属于其他单词的节点。具体规则如下:

  1. 如果单词不存在于 Trie 中,无需操作
  2. 如果被删除的单词是某个更长单词的前缀(如删除 "car""care" 存在),只需取消标记 isEndOfWord,不删除任何节点
  3. 如果被删除的单词的节点不与其他单词共享,则从叶子向根逐个删除无用节点,直到遇到被其他单词使用的节点

C++ 删除实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) children[i] = nullptr;
        isEndOfWord = false;
    }
};

void insert(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            curr->children[index] = new TrieNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

bool search(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr) return false;
        curr = curr->children[index];
    }
    return curr->isEndOfWord;
}

// Check if a node has any children
bool isEmpty(TrieNode* node) {
    for (int i = 0; i < 26; i++) {
        if (node->children[i] != nullptr)
            return false;
    }
    return true;
}

// Recursive delete: returns the node to be deleted (or nullptr)
TrieNode* deleteHelper(TrieNode* root, const string& word, int depth) {
    if (root == nullptr) return nullptr;

    if (depth == (int)word.size()) {
        // reached the last character of the word
        if (root->isEndOfWord)
            root->isEndOfWord = false;  // unmark end of word

        // if node has no children, it can be safely deleted
        if (isEmpty(root)) {
            delete root;
            return nullptr;
        }
        return root;
    }

    int index = word[depth] - 'a';
    root->children[index] = deleteHelper(root->children[index], word, depth + 1);

    // after child deletion, check if current node is now useless
    if (isEmpty(root) && !root->isEndOfWord) {
        delete root;
        return nullptr;
    }
    return root;
}

void deleteWord(TrieNode*& root, const string& word) {
    deleteHelper(root, word, 0);
}

int main() {
    TrieNode* root = new TrieNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    cout << "Before deletion:" << endl;
    cout << "  Search 'car': "  << (search(root, "car")  ? "Found" : "Not found") << endl;
    cout << "  Search 'care': " << (search(root, "care") ? "Found" : "Not found") << endl;

    deleteWord(root, "car");
    cout << "\nAfter deleting 'car':" << endl;
    cout << "  Search 'car': "  << (search(root, "car")  ? "Found" : "Not found") << endl;
    cout << "  Search 'care': " << (search(root, "care") ? "Found" : "Not found") << endl;

    deleteWord(root, "dog");
    cout << "\nAfter deleting 'dog':" << endl;
    cout << "  Search 'dog': " << (search(root, "dog") ? "Found" : "Not found") << endl;
    cout << "  Search 'do': "  << (search(root, "do")  ? "Found" : "Not found") << endl;

    return 0;
}

C 删除实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++) node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void insert(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

bool search(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL) return false;
        curr = curr->children[index];
    }
    return curr->isEndOfWord;
}

// Check if a node has any children
bool isEmpty(TrieNode* node) {
    for (int i = 0; i < 26; i++) {
        if (node->children[i] != NULL)
            return false;
    }
    return true;
}

// Recursive delete: returns the node pointer (NULL if deleted)
TrieNode* deleteHelper(TrieNode* root, const char* word, int depth, int len) {
    if (root == NULL) return NULL;

    if (depth == len) {
        // reached the last character of the word
        if (root->isEndOfWord)
            root->isEndOfWord = false;  // unmark end of word

        if (isEmpty(root)) {
            free(root);
            return NULL;
        }
        return root;
    }

    int index = word[depth] - 'a';
    root->children[index] = deleteHelper(root->children[index], word, depth + 1, len);

    // after child deletion, check if current node is now useless
    if (isEmpty(root) && !root->isEndOfWord) {
        free(root);
        return NULL;
    }
    return root;
}

void deleteWord(TrieNode** root, const char* word) {
    *root = deleteHelper(*root, word, 0, strlen(word));
}

int main() {
    TrieNode* root = createNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    printf("Before deletion:\n");
    printf("  Search 'car': %s\n",  search(root, "car")  ? "Found" : "Not found");
    printf("  Search 'care': %s\n", search(root, "care") ? "Found" : "Not found");

    deleteWord(&root, "car");
    printf("\nAfter deleting 'car':\n");
    printf("  Search 'car': %s\n",  search(root, "car")  ? "Found" : "Not found");
    printf("  Search 'care': %s\n", search(root, "care") ? "Found" : "Not found");

    deleteWord(&root, "dog");
    printf("\nAfter deleting 'dog':\n");
    printf("  Search 'dog': %s\n", search(root, "dog") ? "Found" : "Not found");
    printf("  Search 'do': %s\n",  search(root, "do")  ? "Found" : "Not found");

    return 0;
}

Python 删除实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

def insert(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            curr.children[ch] = TrieNode()
        curr = curr.children[ch]
    curr.is_end_of_word = True

def search(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            return False
        curr = curr.children[ch]
    return curr.is_end_of_word

def _delete_helper(node, word, depth):
    if node is None:
        return None

    if depth == len(word):
        # reached the last character of the word
        if node.is_end_of_word:
            node.is_end_of_word = False   # unmark end of word

        # if node has no children, it can be safely deleted
        if not node.children:
            return None
        return node

    ch = word[depth]
    if ch in node.children:
        node.children[ch] = _delete_helper(node.children[ch], word, depth + 1)

        # after child deletion, check if current node is now useless
        if node.children[ch] is None:
            del node.children[ch]

    # if node has no children and is not end of another word, delete it
    if not node.children and not node.is_end_of_word:
        return None
    return node

def delete_word(root, word):
    _delete_helper(root, word, 0)

if __name__ == '__main__':
    root = TrieNode()
    for w in ["cat", "car", "care", "dog", "do"]:
        insert(root, w)

    print("Before deletion:")
    print(f"  Search 'car': {'Found' if search(root, 'car') else 'Not found'}")
    print(f"  Search 'care': {'Found' if search(root, 'care') else 'Not found'}")

    delete_word(root, "car")
    print("\nAfter deleting 'car':")
    print(f"  Search 'car': {'Found' if search(root, 'car') else 'Not found'}")
    print(f"  Search 'care': {'Found' if search(root, 'care') else 'Not found'}")

    delete_word(root, "dog")
    print("\nAfter deleting 'dog':")
    print(f"  Search 'dog': {'Found' if search(root, 'dog') else 'Not found'}")
    print(f"  Search 'do': {'Found' if search(root, 'do') else 'Not found'}")

运行该程序将输出:

Before deletion:
  Search 'car': Found
  Search 'care': Found

After deleting 'car':
  Search 'car': Not found
  Search 'care': Found

After deleting 'dog':
  Search 'dog': Not found
  Search 'do': Found
  • 删除 "car" 后,"care" 仍然存在。因为 "car""care" 的前缀,删除操作只是取消了 r 节点的 isEndOfWord 标记,节点本身被保留(因为 e 子节点仍在使用)
  • 删除 "dog" 后,"do" 仍然存在。g 节点被删除(因为它是叶子且不是单词结尾),但 o 节点被保留(因为它是 "do" 的单词结尾)

Go 删除实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

func insert(root *TrieNode, word string) {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true
}

func search(root *TrieNode, word string) bool {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            return false
        }
        curr = curr.children[index]
    }
    return curr.isEnd
}

// Check if a node has any children
func isEmpty(node *TrieNode) bool {
    for i := 0; i < 26; i++ {
        if node.children[i] != nil {
            return false
        }
    }
    return true
}

// Recursive delete: returns the node to be kept (or nil if deleted)
func deleteHelper(root *TrieNode, word string, depth int) *TrieNode {
    if root == nil {
        return nil
    }

    if depth == len(word) {
        // reached the last character of the word
        if root.isEnd {
            root.isEnd = false        // unmark end of word
        }

        // if node has no children, it can be safely deleted
        if isEmpty(root) {
            return nil
        }
        return root
    }

    index := word[depth] - 'a'
    root.children[index] = deleteHelper(root.children[index], word, depth+1)

    // after child deletion, check if current node is now useless
    if isEmpty(root) && !root.isEnd {
        return nil
    }
    return root
}

func deleteWord(root *TrieNode, word string) {
    deleteHelper(root, word, 0)
}

func foundStr(found bool) string {
    if found {
        return "Found"
    }
    return "Not found"
}

func main() {
    root := &TrieNode{}
    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    fmt.Println("Before deletion:")
    fmt.Printf("  Search 'car': %s\n", foundStr(search(root, "car")))
    fmt.Printf("  Search 'care': %s\n", foundStr(search(root, "care")))

    deleteWord(root, "car")
    fmt.Println("\nAfter deleting 'car':")
    fmt.Printf("  Search 'car': %s\n", foundStr(search(root, "car")))
    fmt.Printf("  Search 'care': %s\n", foundStr(search(root, "care")))

    deleteWord(root, "dog")
    fmt.Println("\nAfter deleting 'dog':")
    fmt.Printf("  Search 'dog': %s\n", foundStr(search(root, "dog")))
    fmt.Printf("  Search 'do': %s\n", foundStr(search(root, "do")))
}

运行该程序将输出:

Before deletion:
  Search 'car': Found
  Search 'care': Found

After deleting 'car':
  Search 'car': Not found
  Search 'care': Found

After deleting 'dog':
  Search 'dog': Not found
  Search 'do': Found

遍历所有单词

遍历(Traverse)所有单词使用深度优先搜索(DFS, Depth-First Search)。从根节点出发,递归访问每个子节点,沿途记录当前路径对应的字符前缀。当遇到标记为单词结尾的节点时,将当前前缀作为一个完整单词输出。

C++ 遍历实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) children[i] = nullptr;
        isEndOfWord = false;
    }
};

void insert(TrieNode* root, const string& word) {
    TrieNode* curr = root;
    for (char ch : word) {
        int index = ch - 'a';
        if (curr->children[index] == nullptr)
            curr->children[index] = new TrieNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// DFS to collect all words in the Trie
void collectWords(TrieNode* node, string prefix) {
    if (node->isEndOfWord) {
        cout << prefix << endl;         // found a complete word
    }
    for (int i = 0; i < 26; i++) {
        if (node->children[i] != nullptr) {
            char nextChar = 'a' + i;
            collectWords(node->children[i], prefix + nextChar);
        }
    }
}

void listAllWords(TrieNode* root) {
    collectWords(root, "");
}

int main() {
    TrieNode* root = new TrieNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    cout << "All words in the Trie:" << endl;
    listAllWords(root);

    return 0;
}

C 遍历实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++) node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void insert(TrieNode* root, const char* word) {
    TrieNode* curr = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

// DFS to collect all words in the Trie
void collectWords(TrieNode* node, char* prefix, int depth) {
    if (node->isEndOfWord) {
        prefix[depth] = '\0';
        printf("%s\n", prefix);          // found a complete word
    }
    for (int i = 0; i < 26; i++) {
        if (node->children[i] != NULL) {
            prefix[depth] = 'a' + i;
            collectWords(node->children[i], prefix, depth + 1);
        }
    }
}

void listAllWords(TrieNode* root) {
    char buffer[256];                    // buffer for building words
    collectWords(root, buffer, 0);
}

int main() {
    TrieNode* root = createNode();
    insert(root, "cat");
    insert(root, "car");
    insert(root, "care");
    insert(root, "dog");
    insert(root, "do");

    printf("All words in the Trie:\n");
    listAllWords(root);

    return 0;
}

Python 遍历实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

def insert(root, word):
    curr = root
    for ch in word:
        if ch not in curr.children:
            curr.children[ch] = TrieNode()
        curr = curr.children[ch]
    curr.is_end_of_word = True

# DFS to collect all words in the Trie
def collect_words(node, prefix, result):
    if node.is_end_of_word:
        result.append(prefix)           # found a complete word
    for ch, child in sorted(node.children.items()):
        collect_words(child, prefix + ch, result)

def list_all_words(root):
    result = []
    collect_words(root, "", result)
    return result

if __name__ == '__main__':
    root = TrieNode()
    for w in ["cat", "car", "care", "dog", "do"]:
        insert(root, w)

    print("All words in the Trie:")
    for word in list_all_words(root):
        print(word)

运行该程序将输出:

All words in the Trie:
car
care
cat
do
dog

输出按字典序排列。这是因为从 a 到 z(或按字母顺序遍历字典键)的 DFS 天然产生了字典序的结果。遍历所有单词的时间复杂度为 O(n),其中 n 为 Trie 中的总字符数(即所有节点的数量)。

Go 遍历实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

func insert(root *TrieNode, word string) {
    curr := root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true
}

// DFS to collect all words in the Trie
func collectWords(node *TrieNode, prefix string) {
    if node.isEnd {
        fmt.Println(prefix)            // found a complete word
    }
    for i := 0; i < 26; i++ {
        if node.children[i] != nil {
            nextChar := string(rune('a' + i))
            collectWords(node.children[i], prefix+nextChar)
        }
    }
}

func listAllWords(root *TrieNode) {
    collectWords(root, "")
}

func main() {
    root := &TrieNode{}
    insert(root, "cat")
    insert(root, "car")
    insert(root, "care")
    insert(root, "dog")
    insert(root, "do")

    fmt.Println("All words in the Trie:")
    listAllWords(root)
}

运行该程序将输出:

All words in the Trie:
car
care
cat
do
dog

完整实现

以下是将上述所有部分组合在一起的完整 Trie 程序。操作包括:插入单词、搜索单词、前缀搜索、列出所有单词、删除单词。

C++ 完整实现

#include <iostream>
#include <string>
using namespace std;

struct TrieNode {
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) children[i] = nullptr;
        isEndOfWord = false;
    }
};

class Trie {
private:
    TrieNode* root;

    void collectWords(TrieNode* node, string prefix) {
        if (node->isEndOfWord)
            cout << "  " << prefix << endl;
        for (int i = 0; i < 26; i++) {
            if (node->children[i] != nullptr)
                collectWords(node->children[i], prefix + char('a' + i));
        }
    }

    TrieNode* deleteHelper(TrieNode* node, const string& word, int depth) {
        if (node == nullptr) return nullptr;
        if (depth == (int)word.size()) {
            if (node->isEndOfWord)
                node->isEndOfWord = false;
            if (isEmpty(node)) {
                delete node;
                return nullptr;
            }
            return node;
        }
        int index = word[depth] - 'a';
        node->children[index] = deleteHelper(node->children[index], word, depth + 1);
        if (isEmpty(node) && !node->isEndOfWord) {
            delete node;
            return nullptr;
        }
        return node;
    }

    bool isEmpty(TrieNode* node) {
        for (int i = 0; i < 26; i++)
            if (node->children[i] != nullptr) return false;
        return true;
    }

public:
    Trie() : root(new TrieNode()) {}

    void insert(const string& word) {
        TrieNode* curr = root;
        for (char ch : word) {
            int index = ch - 'a';
            if (curr->children[index] == nullptr)
                curr->children[index] = new TrieNode();
            curr = curr->children[index];
        }
        curr->isEndOfWord = true;
    }

    bool search(const string& word) {
        TrieNode* curr = root;
        for (char ch : word) {
            int index = ch - 'a';
            if (curr->children[index] == nullptr) return false;
            curr = curr->children[index];
        }
        return curr->isEndOfWord;
    }

    bool startsWith(const string& prefix) {
        TrieNode* curr = root;
        for (char ch : prefix) {
            int index = ch - 'a';
            if (curr->children[index] == nullptr) return false;
            curr = curr->children[index];
        }
        return true;
    }

    void remove(const string& word) {
        deleteHelper(root, word, 0);
    }

    void listAll() {
        collectWords(root, "");
    }
};

int main() {
    Trie trie;

    // insert words
    trie.insert("cat");
    trie.insert("car");
    trie.insert("care");
    trie.insert("dog");
    trie.insert("do");

    cout << "=== All words ===" << endl;
    trie.listAll();

    // search
    cout << "\n=== Search ===" << endl;
    cout << "Search 'cat': " << (trie.search("cat") ? "Found" : "Not found") << endl;
    cout << "Search 'ca': "  << (trie.search("ca")  ? "Found" : "Not found") << endl;
    cout << "Search 'cow': " << (trie.search("cow") ? "Found" : "Not found") << endl;

    // prefix search
    cout << "\n=== Prefix Search ===" << endl;
    cout << "StartsWith 'ca': " << (trie.startsWith("ca") ? "Yes" : "No") << endl;
    cout << "StartsWith 'do': " << (trie.startsWith("do") ? "Yes" : "No") << endl;
    cout << "StartsWith 'ba': " << (trie.startsWith("ba") ? "Yes" : "No") << endl;

    // delete
    cout << "\n=== Delete 'car' ===" << endl;
    trie.remove("car");
    cout << "Search 'car': "  << (trie.search("car")  ? "Found" : "Not found") << endl;
    cout << "Search 'care': " << (trie.search("care") ? "Found" : "Not found") << endl;

    cout << "\n=== All words after deletion ===" << endl;
    trie.listAll();

    return 0;
}

运行该程序将输出:

=== All words ===
  car
  care
  cat
  do
  dog

=== Search ===
Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found

=== Prefix Search ===
StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No

=== Delete 'car' ===
Search 'car': Not found
Search 'care': Found

=== All words after deletion ===
  care
  cat
  do
  dog

C 完整实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct TrieNode {
    struct TrieNode* children[26];
    bool isEndOfWord;
} TrieNode;

typedef struct {
    TrieNode* root;
} Trie;

TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < 26; i++) node->children[i] = NULL;
    node->isEndOfWord = false;
    return node;
}

void trieInit(Trie* trie) {
    trie->root = createNode();
}

void trieInsert(Trie* trie, const char* word) {
    TrieNode* curr = trie->root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL)
            curr->children[index] = createNode();
        curr = curr->children[index];
    }
    curr->isEndOfWord = true;
}

bool trieSearch(Trie* trie, const char* word) {
    TrieNode* curr = trie->root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';
        if (curr->children[index] == NULL) return false;
        curr = curr->children[index];
    }
    return curr->isEndOfWord;
}

bool trieStartsWith(Trie* trie, const char* prefix) {
    TrieNode* curr = trie->root;
    for (int i = 0; prefix[i] != '\0'; i++) {
        int index = prefix[i] - 'a';
        if (curr->children[index] == NULL) return false;
        curr = curr->children[index];
    }
    return true;
}

bool isEmpty(TrieNode* node) {
    for (int i = 0; i < 26; i++)
        if (node->children[i] != NULL) return false;
    return true;
}

TrieNode* deleteHelper(TrieNode* node, const char* word, int depth) {
    if (node == NULL) return NULL;
    if (depth == (int)strlen(word)) {
        if (node->isEndOfWord)
            node->isEndOfWord = false;
        if (isEmpty(node)) {
            free(node);
            return NULL;
        }
        return node;
    }
    int index = word[depth] - 'a';
    node->children[index] = deleteHelper(node->children[index], word, depth + 1);
    if (isEmpty(node) && !node->isEndOfWord) {
        free(node);
        return NULL;
    }
    return node;
}

void trieDelete(Trie* trie, const char* word) {
    trie->root = deleteHelper(trie->root, word, 0);
}

void collectWords(TrieNode* node, char* buffer, int depth) {
    if (node->isEndOfWord) {
        buffer[depth] = '\0';
        printf("  %s\n", buffer);
    }
    for (int i = 0; i < 26; i++) {
        if (node->children[i] != NULL) {
            buffer[depth] = 'a' + i;
            collectWords(node->children[i], buffer, depth + 1);
        }
    }
}

void trieListAll(Trie* trie) {
    char buffer[256];
    collectWords(trie->root, buffer, 0);
}

int main() {
    Trie trie;
    trieInit(&trie);

    trieInsert(&trie, "cat");
    trieInsert(&trie, "car");
    trieInsert(&trie, "care");
    trieInsert(&trie, "dog");
    trieInsert(&trie, "do");

    printf("=== All words ===\n");
    trieListAll(&trie);

    printf("\n=== Search ===\n");
    printf("Search 'cat': %s\n", trieSearch(&trie, "cat") ? "Found" : "Not found");
    printf("Search 'ca': %s\n",  trieSearch(&trie, "ca")  ? "Found" : "Not found");
    printf("Search 'cow': %s\n", trieSearch(&trie, "cow") ? "Found" : "Not found");

    printf("\n=== Prefix Search ===\n");
    printf("StartsWith 'ca': %s\n", trieStartsWith(&trie, "ca") ? "Yes" : "No");
    printf("StartsWith 'do': %s\n", trieStartsWith(&trie, "do") ? "Yes" : "No");
    printf("StartsWith 'ba': %s\n", trieStartsWith(&trie, "ba") ? "Yes" : "No");

    printf("\n=== Delete 'car' ===\n");
    trieDelete(&trie, "car");
    printf("Search 'car': %s\n",  trieSearch(&trie, "car")  ? "Found" : "Not found");
    printf("Search 'care': %s\n", trieSearch(&trie, "care") ? "Found" : "Not found");

    printf("\n=== All words after deletion ===\n");
    trieListAll(&trie);

    return 0;
}

运行该程序将输出:

=== All words ===
  car
  care
  cat
  do
  dog

=== Search ===
Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found

=== Prefix Search ===
StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No

=== Delete 'car' ===
Search 'car': Not found
Search 'care': Found

=== All words after deletion ===
  care
  cat
  do
  dog

Python 完整实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False


class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        curr = self.root
        for ch in word:
            if ch not in curr.children:
                curr.children[ch] = TrieNode()
            curr = curr.children[ch]
        curr.is_end_of_word = True

    def search(self, word):
        curr = self.root
        for ch in word:
            if ch not in curr.children:
                return False
            curr = curr.children[ch]
        return curr.is_end_of_word

    def starts_with(self, prefix):
        curr = self.root
        for ch in prefix:
            if ch not in curr.children:
                return False
            curr = curr.children[ch]
        return True

    def _delete_helper(self, node, word, depth):
        if node is None:
            return None
        if depth == len(word):
            if node.is_end_of_word:
                node.is_end_of_word = False
            if not node.children:
                return None
            return node

        ch = word[depth]
        if ch in node.children:
            node.children[ch] = self._delete_helper(
                node.children[ch], word, depth + 1)
            if node.children[ch] is None:
                del node.children[ch]

        if not node.children and not node.is_end_of_word:
            return None
        return node

    def delete(self, word):
        self._delete_helper(self.root, word, 0)

    def _collect_words(self, node, prefix, result):
        if node.is_end_of_word:
            result.append(prefix)
        for ch in sorted(node.children.keys()):
            self._collect_words(node.children[ch], prefix + ch, result)

    def list_all(self):
        result = []
        self._collect_words(self.root, "", result)
        return result


if __name__ == '__main__':
    trie = Trie()

    trie.insert("cat")
    trie.insert("car")
    trie.insert("care")
    trie.insert("dog")
    trie.insert("do")

    print("=== All words ===")
    for word in trie.list_all():
        print(f"  {word}")

    print("\n=== Search ===")
    print(f"Search 'cat': {'Found' if trie.search('cat') else 'Not found'}")
    print(f"Search 'ca': {'Found' if trie.search('ca') else 'Not found'}")
    print(f"Search 'cow': {'Found' if trie.search('cow') else 'Not found'}")

    print("\n=== Prefix Search ===")
    print(f"StartsWith 'ca': {'Yes' if trie.starts_with('ca') else 'No'}")
    print(f"StartsWith 'do': {'Yes' if trie.starts_with('do') else 'No'}")
    print(f"StartsWith 'ba': {'Yes' if trie.starts_with('ba') else 'No'}")

    print("\n=== Delete 'car' ===")
    trie.delete("car")
    print(f"Search 'car': {'Found' if trie.search('car') else 'Not found'}")
    print(f"Search 'care': {'Found' if trie.search('care') else 'Not found'}")

    print("\n=== All words after deletion ===")
    for word in trie.list_all():
        print(f"  {word}")

运行该程序将输出:

=== All words ===
  car
  care
  cat
  do
  dog

=== Search ===
Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found

=== Prefix Search ===
StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No

=== Delete 'car' ===
Search 'car': Not found
Search 'care': Found

=== All words after deletion ===
  care
  cat
  do
  dog

三个语言版本的输出完全一致。删除 "car" 后,"care" 依然保留,说明共享前缀的节点被正确保护;"cat", "do", "dog" 不受影响。

Go 完整实现

package main

import "fmt"

type TrieNode struct {
    children [26]*TrieNode
    isEnd    bool
}

type Trie struct {
    root *TrieNode
}

func NewTrie() *Trie {
    return &Trie{root: &TrieNode{}}
}

func (t *Trie) Insert(word string) {
    curr := t.root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            curr.children[index] = &TrieNode{}
        }
        curr = curr.children[index]
    }
    curr.isEnd = true
}

func (t *Trie) Search(word string) bool {
    curr := t.root
    for _, ch := range word {
        index := ch - 'a'
        if curr.children[index] == nil {
            return false
        }
        curr = curr.children[index]
    }
    return curr.isEnd
}

func (t *Trie) StartsWith(prefix string) bool {
    curr := t.root
    for _, ch := range prefix {
        index := ch - 'a'
        if curr.children[index] == nil {
            return false
        }
        curr = curr.children[index]
    }
    return true
}

func (t *Trie) Remove(word string) {
    t.deleteHelper(t.root, word, 0)
}

func (t *Trie) deleteHelper(node *TrieNode, word string, depth int) *TrieNode {
    if node == nil {
        return nil
    }
    if depth == len(word) {
        if node.isEnd {
            node.isEnd = false
        }
        if isEmpty(node) {
            return nil
        }
        return node
    }
    index := word[depth] - 'a'
    node.children[index] = t.deleteHelper(node.children[index], word, depth+1)
    if isEmpty(node) && !node.isEnd {
        return nil
    }
    return node
}

func (t *Trie) ListAll() {
    t.collectWords(t.root, "")
}

func (t *Trie) collectWords(node *TrieNode, prefix string) {
    if node.isEnd {
        fmt.Printf("  %s\n", prefix)
    }
    for i := 0; i < 26; i++ {
        if node.children[i] != nil {
            nextChar := string(rune('a' + i))
            t.collectWords(node.children[i], prefix+nextChar)
        }
    }
}

func isEmpty(node *TrieNode) bool {
    for i := 0; i < 26; i++ {
        if node.children[i] != nil {
            return false
        }
    }
    return true
}

func foundStr(found bool) string {
    if found {
        return "Found"
    }
    return "Not found"
}

func yesNo(val bool) string {
    if val {
        return "Yes"
    }
    return "No"
}

func main() {
    trie := NewTrie()

    // insert words
    trie.Insert("cat")
    trie.Insert("car")
    trie.Insert("care")
    trie.Insert("dog")
    trie.Insert("do")

    fmt.Println("=== All words ===")
    trie.ListAll()

    // search
    fmt.Println("\n=== Search ===")
    fmt.Printf("Search 'cat': %s\n", foundStr(trie.Search("cat")))
    fmt.Printf("Search 'ca': %s\n", foundStr(trie.Search("ca")))
    fmt.Printf("Search 'cow': %s\n", foundStr(trie.Search("cow")))

    // prefix search
    fmt.Println("\n=== Prefix Search ===")
    fmt.Printf("StartsWith 'ca': %s\n", yesNo(trie.StartsWith("ca")))
    fmt.Printf("StartsWith 'do': %s\n", yesNo(trie.StartsWith("do")))
    fmt.Printf("StartsWith 'ba': %s\n", yesNo(trie.StartsWith("ba")))

    // delete
    fmt.Println("\n=== Delete 'car' ===")
    trie.Remove("car")
    fmt.Printf("Search 'car': %s\n", foundStr(trie.Search("car")))
    fmt.Printf("Search 'care': %s\n", foundStr(trie.Search("care")))

    fmt.Println("\n=== All words after deletion ===")
    trie.ListAll()
}

运行该程序将输出:

=== All words ===
  car
  care
  cat
  do
  dog

=== Search ===
Search 'cat': Found
Search 'ca': Not found
Search 'cow': Not found

=== Prefix Search ===
StartsWith 'ca': Yes
StartsWith 'do': Yes
StartsWith 'ba': No

=== Delete 'car' ===
Search 'car': Not found
Search 'care': Found

=== All words after deletion ===
  care
  cat
  do
  dog

Go 版本使用 Trie 结构体封装所有操作,通过 NewTrie() 构造函数初始化。方法接收者使用指针类型 *Trie,确保对内部状态的修改生效。输出与其他语言版本完全一致。


Trie 的性质

时间复杂度

操作 时间复杂度 说明
插入(Insert) O(m) m 为待插入字符串的长度
搜索(Search) O(m) m 为待搜索字符串的长度
前缀搜索(StartsWith) O(m) m 为前缀长度
删除(Delete) O(m) m 为待删除字符串的长度

与哈希表(Hash Table)相比,Trie 的时间复杂度不依赖哈希函数,且不存在哈希冲突(Hash Collision)。与二叉搜索树(BST)相比,Trie 的查找不依赖字符串的全序比较,而是逐字符精确匹配。

空间复杂度

方面 复杂度 说明
最坏情况 O(N * m) N 为单词数,m 为平均长度,所有单词无公共前缀
最佳情况 O(m) 所有单词共享前缀,N 个单词仅需一条链

对于固定数组实现(如 children[26]),每个节点占用恒定空间。如果字符集更大(如 Unicode),应使用字典(dict)或哈希表实现子节点映射,以避免浪费内存。

实际应用

应用 说明
自动补全(Autocomplete) 搜索引擎输入框的联想词功能。用户输入前缀后,Trie 可以快速定位到所有以该前缀开头的单词
拼写检查(Spell Checker) 将字典中的所有单词存入 Trie,检查输入的单词是否存在即可判断拼写是否正确
IP 路由(IP Routing) 路由器使用 Trie 的变体(前缀树)进行最长前缀匹配,确定数据包的转发路径
T9 文字输入(T9 Predictive Text) 手机九宫格键盘的文字预测功能,基于 Trie 查找按键序列对应的所有可能单词
字符串排序(String Sorting) 将所有字符串插入 Trie 后按字典序遍历,即可完成排序,时间复杂度为 O(N * m)
基因序列分析(DNA Sequence Analysis) 在生物信息学中,Trie 可用于存储和检索 DNA 序列(A, T, C, G)的公共前缀
posted @ 2026-04-16 17:53  游翔  阅读(6)  评论(0)    收藏  举报