当前位置:首页 > 技术 > 正文内容

Trie树解决字符串前缀和重复查询问题

访客 技术 2026年6月8日 2

问题背景

在字符串处理中,经常需要高效地完成以下任务:

  • 判断一个字符串是否在字典中出现过
  • 处理重复查询(如点名系统中的重复呼叫)
  • 检查是否存在某个字符串是另一个字符串的前缀

下面通过两个具体问题讲解Trie树的实现方法。

问题一:点名系统(P2580)

给定一个学生名单,老师逐个点名,需要判断每个名字的状态:第一次被点到输出"OK",重复被点到输出"REPEAT",不存在于名单中输出"WRONG"。利用Trie树存储名字并记录访问次数

C++实现

#include <bits/stdc++.h>
using namespace std;

const int MAX_NODES = 5e5 + 5;
int node_count = 0;

struct TrieNode {
    int child[27];
    bool is_end;
    int visit_count;
} trie[MAX_NODES];

void add_string(const string& s) {
    int cur = 0;
    for (char ch : s) {
        int idx = ch - 'a';
        if (!trie[cur].child[idx])
            trie[cur].child[idx] = ++node_count;
        cur = trie[cur].child[idx];
    }
    trie[cur].is_end = true;
}

int query_string(const string& s) {
    int cur = 0;
    for (char ch : s) {
        int idx = ch - 'a';
        if (!trie[cur].child[idx])
            return 3; // 不存在
        cur = trie[cur].child[idx];
    }
    if (!trie[cur].is_end)
        return 3; // 不完整匹配
    if (trie[cur].visit_count == 0) {
        trie[cur].visit_count++;
        return 1; // 首次出现
    }
    return 2; // 重复出现
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n, m;
    string name;
    
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> name;
        add_string(name);
    }
    
    cin >> m;
    while (m--) {
        cin >> name;
        int result = query_string(name);
        if (result == 1) cout << "OK\n";
        else if (result == 2) cout << "REPEAT\n";
        else cout << "WRONG\n";
    }
    
    return 0;
}

问题二:前缀判断(UVA11362 Phone List)

给定一组电话号码,判断是否存在一个号码是另一个号码的前缀。例如,"911"是"91125426"的前缀。这里需要在Trie中记录每个节点经过的字符串数量。

C++实现

#include <bits/stdc++.h>
using namespace std;

const int MAX_NODES = 4e5 + 5;

struct TrieNode {
    int child[11];
    int prefix_count;
    bool is_end;
    
    void reset() {
        memset(child, 0, sizeof(child));
        prefix_count = 0;
        is_end = false;
    }
} trie[MAX_NODES];

int total_nodes = 0;

void clear_all() {
    for (int i = 0; i < MAX_NODES; i++)
        trie[i].reset();
    total_nodes = 0;
}

void insert_number(const string& s) {
    int cur = 0;
    for (char ch : s) {
        int idx = ch - '0';
        if (!trie[cur].child[idx])
            trie[cur].child[idx] = ++total_nodes;
        trie[cur].prefix_count++;
        cur = trie[cur].child[idx];
    }
    trie[cur].is_end = true;
}

bool check_prefix(const string& s) {
    int cur = 0;
    for (char ch : s) {
        int idx = ch - '0';
        if (!trie[cur].child[idx])
            return false;
        cur = trie[cur].child[idx];
    }
    return trie[cur].prefix_count > 0;
}

int main() {
    int test_cases;
    cin >> test_cases;
    
    while (test_cases--) {
        clear_all();
        
        int n;
        cin >> n;
        vector<string> numbers(n);
        
        for (int i = 0; i < n; i++) {
            cin >> numbers[i];
            insert_number(numbers[i]);
        }
        
        bool has_prefix = false;
        for (const string& s : numbers) {
            if (check_prefix(s)) {
                has_prefix = true;
                break;
            }
        }
        
        cout << (has_prefix ? "NO\n" : "YES\n");
    }
    
    return 0;
}

核心思想

  • 插入操作:从根节点开始,根据字符逐步向下创建或遍历子节点,并在结尾标记。
  • 查询操作:沿路径查找,检查是否存在完整匹配或前缀,同时可记录访问次数。
  • 前缀检测:在插入时累计经过的节点次数,查询时通过判断某个节点是否有子节点或计数来确认前缀关系。

Trie树能以O(L)时间复杂度处理字符串(L为字符串长度),适用于大量字符串查找、前缀匹配等场景。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。