一文吃透 C++ 里的各种“匹配”

一文吃透 C++ 里的各种“匹配”


目录

  1. 前言:为什么要分“查找”和“匹配”?
  2. 标准算法篇
    1-1 std::find_if 1-2 std::any_of / all_of / none_of
  3. 正则表达式篇:std::regex
  4. 哈希表极速匹配篇:std::unordered_set / std::unordered_map
  5. 自定义算法篇:自己写循环也能优雅
  6. 前缀 & 批量关键词篇
    5-1 Trie 5-2 Radix Tree 5-3 TST 5-4 Aho–Corasick
  7. 子串 & 模糊匹配篇
    6-1 Suffix Array 6-2 BK-Tree
  8. 选型速查表 + 三行口诀
  9. 练习清单 & 常见陷阱

0️⃣ 前言:查找 vs. 匹配

概念 生活类比 代码里典型场景
查找 (find) 通讯录里翻号码——只看“是不是同一个名字” set.find("Alice")
匹配 (match) 找“姓王、手机号以 188 开头”的人 find_if、正则、Trie …

一句话记忆

  • 查找 = 精确相等
  • 匹配 = 满足条件,条件可以很花哨。

1️⃣ 标准算法篇(C++ 自带,头文件

1-1 std::find_if —— 找到第一个符合条件的元素

#include    // find_if 所在头
#include 
#include 
#include 

struct Person {
    std::string name;  // 姓名
    int         age;   // 年龄
};

int main() {
    // ① 准备数据
    std::vector<Person> people = {
        {"Alice",   25},
        {"Bob",     30},
        {"Charlie", 35}
    };

    // ② 调用 find_if:
    //    • people.begin() / people.end()   -> 搜索范围
    //    • Lambda 表达式                  -> 匹配条件
    auto it = std::find_if(people.begin(), people.end(),
                           [](const Person& p) {
                               return p.age > 30;   // ③ 条件:年龄 > 30
                           });

    // ④ 判断有没有找到
    if (it != people.end()) {
        std::cout << "找到的人叫 " << it->name << '\n';
    } else {
        std::cout << "没人符合条件\n";
    }
}

时间复杂度:看元素个数,最坏 O(n)
何时用:要找“符合某个复杂条件”的第一条记录


1-2 std::any_of / all_of / none_of —— 一句话问全局

#include 
#include 
#include 

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};

    // 任意元素 > 3?
    bool anyGT3 = std::any_of(nums.begin(), nums.end(),
                              [](int x){ return x > 3; });

    // 所有元素都是奇数?
    bool allOdd = std::all_of(nums.begin(), nums.end(),
                              [](int x){ return x % 2 == 1; });

    // 没有元素为 0?
    bool noneZero = std::none_of(nums.begin(), nums.end(),
                                 [](int x){ return x == 0; });

    std::cout << std::boolalpha  // 打开布尔的 true/false 输出
              << "anyGT3  = " << anyGT3  << '\n'
              << "allOdd  = " << allOdd  << '\n'
              << "noneZero= " << noneZero<< '\n';
}

场景

  • 表格校验:“是否所有分数≥60?”
  • 配置检查:“有没有字段缺失?”

2️⃣ 正则表达式篇:std::regex

正则 = 用一段特殊字符串描述“字符串长什么样”。工具刀非常锋利,新手建议先小用再深入。

#include       // regex、smatch 等
#include 
#include 

int main() {
    std::string text = "邮箱是 [email protected],记一下";
    // ① 邮箱正则:原生字符串 R"(…)" 省去反斜杠转义
    std::regex mailRe(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");

    std::smatch match;                          // ② 保存结果
    bool found = std::regex_search(text, match, // ③ 搜索
                                   mailRe);     //    按 mailRe 匹配

    if (found) {
        std::cout << "找到了邮箱:" << match.str() << '\n'; // match.str() = 子串
    } else {
        std::cout << "没找到邮箱\n";
    }
}

优点:一句话搞定复杂格式。
缺点:语法难记,超长正则性能差。


3️⃣ 哈希表极速匹配篇:std::unordered_set / map

#include 
#include 
#include 

int main() {
    // ① 把合法邮箱放进哈希表,底层是数组 + 哈希函数
    std::unordered_set<std::string> whitelist = {
        "[email protected]",
        "[email protected]"
    };

    std::string email = "[email protected]";

    // ② 查找:O(1) 平均复杂度
    bool ok = whitelist.find(email) != whitelist.end();
    std::cout << (ok ? "合法邮箱\n" : "非法邮箱\n");
}

适用:关键词量大、查询频繁、要秒级响应。

自定义类型要自己提供 ==std::hash<>,否则编译不过。


4️⃣ 自定义算法篇:写循环也能优雅

#include 
#include 
#include 

struct Log {
    int    level;   // 日志级别
    std::string msg;
};

// 匹配条件:级别是偶数且包含 "ERROR"
bool match(const Log& log) {
    return log.level % 2 == 0 &&
           log.msg.find("ERROR") != std::string::npos;
}

int main() {
    std::vector<Log> logs = {
        {1, "INFO start"},
        {2, "ERROR failed opening file"},
        {3, "DEBUG ..."}
    };

    for (const auto& l : logs) {  // 范围 for:从头到尾
        if (match(l)) {
            std::cout << "命中日志:「" << l.msg << "」\n";
        }
    }
}

经验

  • 规则多变 → 写函数最灵活。
  • 代码可读 → 日后同事谢谢你。

5️⃣ 前缀 & 批量关键词篇

适合“自动补全”“敏感词过滤”“输入一篇文章一次找很多词”。

5-1 Trie(前缀树)——新手能看懂的最小实现

#include 
#include 
#include 
#include 

struct Node {                      // 节点
    bool end = false;              // 是否单词结束
    std::unordered_map<char, Node*> next;
};

class Trie {
    std::unique_ptr<Node> root = std::make_unique<Node>();
public:
    void insert(const std::string& word) {
        Node* cur = root.get();
        for (char c : word) {
            if (!cur->next[c]) cur->next[c] = new Node; // 没有就建
            cur = cur->next[c];                         // 指针下移
        }
        cur->end = true;
    }
    bool startsWith(const std::string& pre) const {
        const Node* cur = root.get();
        for (char c : pre) {
            auto it = cur->next.find(c);
            if (it == cur->next.end()) return false;    // 断路
            cur = it->second;
        }
        return true;                                    // 所有字符都走通
    }
};

int main() {
    Trie t;
    t.insert("hello");
    t.insert("helium");

    std::cout << std::boolalpha
              << "he 前缀存在? " << t.startsWith("he") << '\n'; // true
}

5-2 Radix Tree(压缩 Trie)

  • 原理:把“独苗”节点压缩成一条长边 → 省内存。
  • API 与 Trie 类似,只是实现更复杂。

5-3 Ternary Search Tree(TST)

  • 结构:字符放中间节点,左<右> 形成三叉树。
  • 特点:内存介于 Trie 与有序字符串数组之间。

5-4 Aho–Corasick 自动机(AC 自动机)

  • 场景:一次扫描文本,找上千个关键词也只 O(文本长度)
  • 思路:Trie + 失配指针(像 KMP)。
  • boost::algorithm::aho_corasick Header-only,一行 #include 即用。

6️⃣ 子串 & 模糊匹配篇

6-1 Suffix Array(后缀数组)

  • 把所有“后缀”排字典序,二分就能查任意子串。
  • 优势:省内存、实现比 Suffix Tree 简单。
  • 用例:全文搜索、高性能 DNA 比对。

6-2 BK-Tree(Burkhard-Keller)

  • 节点存一个词,边权 = 两词“编辑距离”。
  • 查询“距离 ≤ k”时只走合理分支 → 速度近似 O(log n)
  • 适合:模糊搜索、拼写纠正(如 gril → girl)。

7️⃣ 选型速查表

技术 复杂度* 你什么时候用 标准库?
find_if O(n) 条件、找第一条
any_of O(n) 判断整体真/假
regex 与模式相关 检查 & 抽取字符串格式
unordered_set/map O(1) 平均 精确值存在性、键查值
Trie O(词长) 前缀匹配、补全
Radix / TST O(词长) 前缀匹配且想省内存
AC 自动机 O(文本长) 批量关键词一次过滤
Suffix Array O(子串长 log n) 任意子串检索
BK-Tree O(log n) 模糊(编辑距离)搜索

* 粗略估计;常数、数据量对真实速度影响很大。

三行口诀

精确 → 哈希
复杂条件 → 算法 / 正则
批量前缀 / 多关键词 → Trie / AC
大文本子串 → 后缀数组
模糊相似 → BK-Tree

8️⃣ 常见陷阱

易踩坑

  1. std::regexGCC 4.x 很慢,老服务器慎用。
  2. Trie 如果每节点 new,百万关键词会爆内存——用节点池双数组压缩
  3. 简单需求盲目上 AC 或后缀数组:构建成本 > 收益。先问自己数据量多大、性能瓶颈在哪。

结语

匹配手段很多,但80% 的场景只要标准库就够。当数据规模、实时性、功能复杂度逐渐上升,再把 Trie、AC、Suffix Array 等“专业武器”搬进来。

选最简单的方案先跑起来 → 基准测试 → 再升级

你可能感兴趣的:(C,c++,开发语言)