C++常用算法及数据结构(字符串匹配算法)

字符串匹配算法

字符串匹配算法是指在一个文本串(较长的字符串)中查找一个模式串(较短的字符串)的出现位置或匹配情况的算法。以下是几种常见的字符串匹配算法

1.朴素字符串匹配算法(Brute-Force算法):

从文本串的首字母开始依次与模式串作比较,直到找到匹配或者比较到文本串末尾。时间复杂度为O(nm),其中n为文本串长度,m为模式串长度。

示例代码:
 
  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. // 定义匹配结果结构体
  5. struct MatchResult {
  6. bool found; // 是否找到匹配
  7. int startIndex; // 匹配起始索引
  8. int endIndex; // 匹配结束索引
  9. };
  10. // 优化后的朴素字符串匹配算法函数
  11. MatchResult bruteForceSearch(const string& text, const string& pattern) {
  12. int n = text.length(); // 文本串的长度
  13. int m = pattern.length(); // 模式串的长度
  14. int startIndex = -1; // 匹配起始索引
  15. int endIndex = -1; // 匹配结束索引
  16. // 在文本串中滑动窗口
  17. for (int i = 0; i <= n - m; i++) {
  18. int j;
  19. // 逐个字符比较
  20. for (j = 0; j < m; j++) {
  21. if (text[i + j] != pattern[j])
  22. break;
  23. }
  24. // 找到完全匹配
  25. if (j == m) {
  26. startIndex = i; // 设置匹配起始索引
  27. endIndex = i + m - 1; // 设置匹配结束索引
  28. break;
  29. }
  30. }
  31. // 构造匹配结果结构体
  32. MatchResult result;
  33. if (startIndex != -1) {
  34. result.found = true;
  35. result.startIndex = startIndex;
  36. result.endIndex = endIndex;
  37. } else {
  38. result.found = false;
  39. result.startIndex = -1;
  40. result.endIndex = -1;
  41. }
  42. return result;
  43. }
  44. int main() {
  45. string text = "ABCABDABEABF"; // 文本串
  46. string pattern = "ABD"; // 模式串
  47. MatchResult result = bruteForceSearch(text, pattern); // 调用优化后的朴素字符串匹配算法函数
  48. // 根据匹配结果进行输出
  49. if (result.found) {
  50. cout << "从文本串的 " << result.startIndex << " 到 " << result.endIndex << endl;
  51. } else {
  52. cout << "未找到匹配的字符串" << endl;
  53. }
  54. return 0;
  55. }
代码解析:
  1. 定义了一个名为 MatchResult 的结构体,用于存储匹配结果的相关信息。结构体包含三个成员变量:found 表示是否找到匹配,startIndex 表示匹配的起始索引,endIndex 表示匹配的结束索引。
  2. 定义了一个名为 bruteForceSearch 的函数,用于执行优化后的朴素字符串匹配算法。函数的输入参数为文本串 text 和模式串 pattern,返回一个 MatchResult 结构体,表示匹配结果。
  3. 在函数内部,我们首先获取文本串和模式串的长度,初始化匹配的起始索引 startIndex 和结束索引 endIndex 为 -1。
  4. 然后,我们使用两层循环:外层循环控制滑动窗口的起始位置,内层循环逐个字符比较文本串和模式串的对应字符。
  5. 如果在内层循环中找到了完全匹配,即内层循环正常结束(j == m),我们设置匹配的起始索引为当前滑动窗口的起始位置 i,结束索引为起始索引加上模式串的长度减1。
  6. 当找到完全匹配后,我们跳出外层循环(使用 break 语句)。
  7. 接下来,我们根据匹配的起始索引是否为 -1 分别设置 MatchResult 结构体的 found 字段为 true 或 false,并将匹配的索引值赋值给 startIndex 和 endIndex。
  8. 最后,我们返回构造好的 MatchResult 结构体给调用者。
  9. 在 main 函数中,我们定义了一个文本串 text 和一个模式串 pattern。
  10. 然后,我们调用优化后的朴素字符串匹配算法函数 bruteForceSearch 并将其返回的结果存储在 result 中。
  11. 最后,我们根据匹配结果的 found 字段进行判断输出。如果 found 为 true,表示找到了匹配,我们输出匹配的起始索引 startIndex 和结束索引 endIndex。如果 found 为 false,表示未找到匹配,我们输出相应的提示信息。
    这样,以上代码实现了一个简单的朴素字符串匹配算法,并通过 MatchResult 结构体将匹配的结果返回给主函数以进行进一步处理和输出。
    运行结果

2.KMP算法:

KMP算法利用模式串的特性,通过建立一个部分匹配表(Partial Match Table)来避免在模式串和文本串中的不必要的匹配和回溯。它的时间复杂度为O(n+m),其中n为文本串长度,m为模式串长度。

示例代码:
 
  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. using namespace std;
  5. vector<int> getFailureFunction(const string& pattern) {
  6. int m = pattern.length(); // 模式串的长度
  7. vector<int> failure(m, 0); // 部分匹配表
  8. int j = 0;
  9. for (int i = 1; i < m; i++) {
  10. if (pattern[i] == pattern[j]) {
  11. failure[i] = j + 1;
  12. j++;
  13. } else {
  14. if (j != 0) {
  15. j = failure[j - 1];
  16. i--;
  17. } else {
  18. failure[i] = 0;
  19. }
  20. }
  21. }
  22. return failure;
  23. }
  24. vector<int> kmpSearch(const string& text, const vector<string>& patterns) {
  25. int n = text.length(); // 文本串的长度
  26. vector<int> positions; // 存储匹配的位置信息
  27. for (const string& pattern : patterns) {
  28. int m = pattern.length(); // 模式串的长度
  29. vector<int> failure = getFailureFunction(pattern); // 构建部分匹配表
  30. int i = 0, j = 0;
  31. while (i < n) {
  32. if (text[i] == pattern[j]) {
  33. if (j == m - 1) { // 完全匹配
  34. positions.push_back(i - j); // 存储匹配的起始索引
  35. break;
  36. } else {
  37. i++;
  38. j++;
  39. }
  40. } else {
  41. if (j != 0) { // 部分匹配,根据部分匹配表来移动模式串
  42. j = failure[j - 1];
  43. } else {
  44. i++;
  45. }
  46. }
  47. }
  48. }
  49. return positions;
  50. }
  51. int main() {
  52. string text = "ABCABDABEABF"; // 文本串
  53. vector<string> patterns = {"ABD", "BCA", "ABE"}; // 多个模式串
  54. vector<int> positions = kmpSearch(text, patterns); //
  55. if (positions.empty()) {
  56. cout << "未找到匹配的字符串" << endl; // 未找到匹配
  57. } else {
  58. for (int i : positions) {
  59. cout << "从文本串的" << i << " 到 " << i + patterns[0].length() - 1 << endl;
  60. // 打印匹配的起始索引和结束索引(这里假设所有模式串的长度相同)
  61. }
  62. }
  63. return 0;
  64. }
代码解析:
  1. getFailureFunction 函数实现了构建模式串的部分匹配表。该表存储了每个位置字符前缀和后缀相等的最长部分匹配长度。使用双指针 i 和 j 遍历模式串,根据当前字符是否匹配进行更新。
  2. kmpSearch 函数实现了多个模式串的搜索。对于每个模式串,首先获取其长度 m,然后构建部分匹配表。使用双指针 i 和 j 在文本串中进行匹配的遍历。如果当前字符匹配,继续向后匹配,直到完全匹配。如果不匹配,根据部分匹配表移动模式串的指针。一旦找到完全匹配,将匹配的起始索引保存到 positions 中。
  3. 在 main 函数中,定义了文本串 text 和多个模式串的向量 patterns,分别表示要搜索的文本和模式串。调用 kmpSearch 函数获取匹配的位置信息,并将结果保存到 positions 中。

    运行结果

    小拓展:
     
      
    1. for (int i : positions) {
    2. cout << "从文本串的" << i << " 到 " << i + patterns[0].length() - 1 << endl;

    上面的代码是C++11中的foreach循环。
    其基本的结构为

     
      
    1. for (element : container) {
    2. // 循环体,对每个 element 进行操作
    3. }

    其中,container 是一个可迭代的容器(如 vector、list、array 等),element 是容器中的每个元素。
    在循环的每次迭代中,element 会依次被赋值为容器中的每个元素,并执行循环体内的操作。

    下面是简单的例子:
     
      
    1. vector<int> numbers = {1, 2, 3, 4, 5};
    2. for (int num : numbers) {
    3. cout << num << " ";
    4. //输出结果为1 2 3 4 5
    5. }
    3.Rabin-Karp算法:

    Rabin-Karp算法通过哈希函数对模式串和文本串中的子串分别进行哈希,然后比较哈希值来判断是否匹配。它的时间复杂度为O(n+m),但在最坏情况下可能达到O(nm),其中n为文本串长度,m为模式串长度。

    示例代码:
     
      
    1. #include <iostream>
    2. #include <string>
    3. #include <vector>
    4. using namespace std;
    5. const int prime = 101; // 用于计算哈希值的质数
    6. vector<pair<int, int>> rabinKarpSearch(const string& text, const string& pattern) {
    7. int n = text.length(); // 文本串的长度
    8. int m = pattern.length(); // 模式串的长度
    9. vector<pair<int, int>> positions; // 存储匹配位置信息
    10. int patternHash = 0; // 模式串的哈希值
    11. int windowHash = 0; // 窗口的哈希值
    12. int power = 1; // 用于计算哈希值的幂次
    13. // 计算模式串的哈希值和初始窗口的哈希值
    14. for (int i = 0; i < m; i++) {
    15. patternHash = (patternHash * prime + pattern[i]) % prime;
    16. windowHash = (windowHash * prime + text[i]) % prime;
    17. if (i != 0) {
    18. power = (power * prime) % prime; // 计算 prime^(m-1)
    19. }
    20. }
    21. // 在文本串中滑动窗口并比较哈希值
    22. for (int i = 0; i <= n - m; i++) {
    23. // 如果哈希值匹配并且窗口内的字符串与模式串相同,则找到匹配
    24. if (patternHash == windowHash && text.substr(i, m) == pattern) {
    25. positions.push_back(make_pair(i, i + m - 1));
    26. }
    27. // 滚动更新窗口哈希值
    28. if (i < n - m) {
    29. windowHash = (prime * (windowHash - text[i] * power) + text[i + m]) % prime;
    30. if (windowHash < 0) {
    31. windowHash += prime; // 处理负数情况
    32. }
    33. }
    34. }
    35. return positions;
    36. }
    37. int main() {
    38. string text = "ABCABDABEABF"; // 文本串
    39. string pattern = "ABD"; // 模式串
    40. vector<pair<int, int>> positions = rabinKarpSearch(text, pattern);
    41. if (positions.empty()) {
    42. cout << "未找到匹配的字符串" << endl; // 未找到匹配
    43. } else {
    44. for (auto pos : positions) {
    45. cout << "文本串的 " << pos.first << "到 " << pos.second << endl;
    46. }
    47. }
    48. return 0;
    49. }
    代码解析:

在rabinKarpSearch函数中,首先计算模式串的哈希值和初始窗口的哈希值。然后,在文本串中滑动窗口,比较窗口的哈希值与模式串的哈希值是否相等,以及窗口内的字符串与模式串是否相等。如果相等,则将匹配的起始索引和结束索引存储到positions向量中。

在main函数中,定义了一个文本串text和一个模式串pattern。然后调用rabinKarpSearch函数进行搜索,并将返回的匹配位置存储到positions向量中。如果没有找到匹配的字符串,输出提示信息”未找到匹配的字符串”。如果找到匹配的字符串,遍历positions向量并输出每一个匹配的起始索引和结束索引。
运行结果同上(想偷懒)

4.Aho-Corasick算法:

Aho-Corasick算法是一种多模式匹配算法,用于在一个文本串中同时匹配多个模式串。它利用自动机的思想构建一个Trie树,并通过状态转移和失配函数来处理匹配过程。它的时间复杂度为O(n+m+k),其中n为文本串长度,m为模式串平均长度,k为模式串数量。

示例代码:(写的很烂)
 
  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <algorithm>
  5. using namespace std;
  6. struct TrieNode {
  7. vector<TrieNode*> children;
  8. vector<int> patterns;
  9. TrieNode* fail;
  10. TrieNode* parent;
  11. char ch;
  12. TrieNode(TrieNode* parent, char ch) {
  13. children.resize(26, nullptr);
  14. this->fail = nullptr;
  15. this->parent = parent;
  16. this->ch = ch;
  17. }
  18. };
  19. class AhoCorasick {
  20. public:
  21. AhoCorasick(const vector<string>& patterns) {
  22. root = new TrieNode(nullptr, '\0');
  23. this->patterns = patterns;
  24. buildTrie(this->patterns);
  25. buildFailPointers();
  26. }
  27. vector<pair<int, int>> search(const string& text) {
  28. vector<pair<int, int>> positions;
  29. TrieNode* currNode = root;
  30. for (int i = 0; i < text.length(); i++) {
  31. char ch = text[i];
  32. while (currNode != root && currNode->children[ch - 'a'] == nullptr) {
  33. currNode = currNode->fail;
  34. }
  35. if (currNode->children[ch - 'a'] != nullptr) {
  36. currNode = currNode->children[ch - 'a'];
  37. }
  38. TrieNode* tempNode = currNode;
  39. while (tempNode != root) {
  40. for (int patternIndex : tempNode->patterns) {
  41. int startPos = i - this->patterns[patternIndex].length() + 1;
  42. int endPos = i;
  43. positions.push_back(make_pair(startPos, endPos));
  44. }
  45. tempNode = tempNode->fail;
  46. }
  47. }
  48. return positions;
  49. }
  50. private:
  51. TrieNode* root;
  52. vector<string> patterns;
  53. void buildTrie(const vector<string>& patterns) {
  54. for (int i = 0; i < patterns.size(); i++) {
  55. insertPattern(patterns[i], i);
  56. }
  57. }
  58. void insertPattern(const string& pattern, int patternIndex) {
  59. TrieNode* currNode = root;
  60. for (char ch : pattern) {
  61. if (currNode->children[ch - 'a'] == nullptr) {
  62. TrieNode* newNode = new TrieNode(currNode, ch);
  63. currNode->children[ch - 'a'] = newNode;
  64. }
  65. currNode = currNode->children[ch - 'a'];
  66. }
  67. currNode->patterns.push_back(patternIndex);
  68. }
  69. void buildFailPointers() {
  70. root->fail = root;
  71. vector<TrieNode*> nodeQueue;
  72. for (int i = 0; i < 26; i++) {
  73. if (root->children[i] != nullptr) {
  74. root->children[i]->fail = root;
  75. nodeQueue.push_back(root->children[i]);
  76. }
  77. }
  78. while (!nodeQueue.empty()) {
  79. TrieNode* currNode = nodeQueue.back();
  80. nodeQueue.pop_back();
  81. for (int i = 0; i < 26; i++) {
  82. if (currNode->children[i] != nullptr) {
  83. TrieNode* childNode = currNode->children[i];
  84. nodeQueue.push_back(childNode);
  85. TrieNode* failNode = currNode->fail;
  86. while (failNode != root && failNode->children[i] == nullptr) {
  87. failNode = failNode->fail;
  88. }
  89. if (failNode->children[i] != nullptr) {
  90. failNode = failNode->children[i];
  91. }
  92. childNode->fail = failNode;
  93. }
  94. }
  95. }
  96. }
  97. };
  98. int main() {
  99. vector<string> patterns = { "he", "she", "his", "hers" };
  100. AhoCorasick ahoCorasick(patterns);
  101. string text = "ushershehis";
  102. vector<pair<int, int>> positions = ahoCorasick.search(text);
  103. if (positions.empty()) {
  104. cout << "No patterns found" << endl;
  105. } else {
  106. for (auto pos : positions) {
  107. cout << "Pattern found at positions: " << pos.first << " - " << pos.second << endl;
  108. }
  109. }
  110. return 0;
  111. }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值