• 【数据结构】字符串匹配(kmp算法)


    原理解析:

    主串字符 a 和子串字符 b 匹配,主串和子串指针向后移动。 

    主串字符 b 和子串字符 b 匹配,主串和子串指针向后移动。

    主串字符 a 和 子串字符 c 不匹配,子串指针直接指向 next数组值指定编号字符。

    主串字符 a 和 子串字符 a 匹配,主串和子串指针向后移动。

    主串字符 b 和 子串字符 b 匹配,主串和子串指针向后移动。

    主串字符 c 和 子串字符 c 匹配,主串和子串指针向后移动,这个时候主串和子串的指针都会指向空,判断 kmp 算法的成功标准为  子串指针移动次数等于子串长度。 

    为什么不能用主串指针移动的次数等于主串长度来判断 kmp 算法是否结束?

    原因:子串走完而主串可能还没有与主串匹配成功。

    next 数组怎么确定值?

    公共前后缀:前面后面一样的前后缀。

    前后缀

    eg:字符串 abacaba,其前缀有 a, ab, aba, abac, abacab,后缀有bacaba, acaba, caba, aba, ba, a

    next 数组值 = 公共前后缀的长度 + 1

    假设子串为 ABACCABABB,求这个子串的 next 数组

    第一个开始,遮住第一个到最后一个,看前面有没有公共前后缀。  

    第一个元素字符前面没有内容,所以公共前后缀的长度为 0 ,第一个字符 next 数组值就为 0。

    再从第二个开始,遮住第二个到最后一个,看前面有没有公共前后缀。 

    只有字符 A ,没有公共前后缀,所以公共前后缀的长度为 0 ,第二个字符 next 数组值为 0 + 1 = 1。

    再从第三个开始,遮住第三个到最后一个,看前面有没有公共前后缀。

    字符 A 和 字符 B 没有公共前后缀,所以公共前后缀的长度为 0,第三个字符 next 数组值为 0 + 1 = 1。

    再从第四个开始,遮住第四个到最后一个,看前面有没有公共前后缀。

    字符 A 和 字符 A 有公共前缀,所以公共前后缀长度为 1,第四个字符 next 数组值为 1 + 1 = 2。

    再从第五个开始,遮住第五个到最后一个,看前面有没有公共前后缀。

    字符 A B A C 没有公共前后缀,所以公共前后缀长度为 0,第五个字符 next 数组值为 0 + 1 = 1。

    再从第六个开始,遮住第六个到最后一个,看前面有没有公共前后缀。

    字符 A B A C C 没有公共前后缀,所以公共前后缀长度为 0,第六个字符 next 数组值为 0 + 1 = 1。

    再从第七个开始,遮住第七个到最后一个,看前面有没有公共前后缀。

    字符 A B A C C A 有公共前后缀 A,所以公共前后缀的长度为 1,第七个字符 next 数组值为 1 + 1 = 2。

    再从第八个开始,遮住第八个到最后一个,看前面有没有公共前后缀。

    字符 A B A C C A B 公共前后缀 AB,所以公共前后缀长度为 2,第八个字符 next 数组值为 2 + 1 = 3。

    再从第九个开始,遮住第九个到最后一个,看前面有没有公共前后缀。

    字符 A B A C C A B A 公共前后缀 ABA,所以公共前后缀长度为 3,第九个字符 next 数组值为 3 + 1 = 4。

    再从第十个开始(最后一个),遮住最后一个,看前面有没有公共前后缀。

    字符 A B A C C A B A B 公共前后缀 AB,所以公共前后缀长度为 2,第十个字符 next 数组值为 2 + 1 = 3。

    至此 next 数组求完。 

    求 next数组的代码:

    1. // 计算 next 数组
    2. int* getNext(String* s) {
    3. int* next = (int*)malloc(sizeof(int) * s->size);
    4. int i = 0;
    5. int j = -1;
    6. next[i] = j;
    7. while (i < s->size - 1) {
    8. if (j == -1 || s->data[i] == s->data[j]) {
    9. i++;
    10. j++;
    11. next[i] = j;
    12. }
    13. else {
    14. j = next[j]; // 子串回溯
    15. }
    16. }
    17. return next;
    18. }

    实践代码:

    1. #include
    2. #include
    3. // KMP 算法 相对于 暴力怕破解的优化
    4. // 主串指针没有回溯,快速达到匹配状态
    5. // KMP 是一种高效的的模式匹配算法,牺牲了一定的空间去保存next数组
    6. // 是当该字符不匹配后,值对应索引的字符要移动到跟主串不匹配的字符对齐
    7. // 算法
    8. // 公共前后缀:前面和后面一样的
    9. // next 值 = 公共前后缀 + 1
    10. // 减少主串指针的移动,快速找到能前后匹配的状态,从而加快匹配的进度
    11. typedef struct String {
    12. char* data;
    13. int size;
    14. }String;
    15. // 初始化串
    16. String* initString() {
    17. String* s = (String*)malloc(sizeof(String));
    18. s->data = NULL;
    19. s->size = 0;
    20. return s;
    21. }
    22. // 添加数据
    23. void stringAssign(String* s, char* data) {
    24. if (s->data) {
    25. free(s->data);
    26. }
    27. int len = 0;
    28. char* temp = data;
    29. while (*temp) {
    30. len++;
    31. temp++;
    32. }
    33. if (len == 0) {
    34. len = 0;
    35. s->data = NULL;
    36. }
    37. else {
    38. temp = data;
    39. s->size = len;
    40. s->data = (char*)malloc(sizeof(char) * (len + 1));
    41. for (int i = 0; i < len; i++, temp++) {
    42. s->data[i] = *temp;
    43. }
    44. }
    45. }
    46. // 输出字符串结构
    47. void printString(String* s) {
    48. for (int i = 0; i < s->size; i++) {
    49. printf(i == 0 ? "%c" : "->%c", s->data[i]);
    50. }
    51. printf("\n");
    52. }
    53. // 计算 next 数组
    54. int* getNext(String* s) {
    55. int* next = (int*)malloc(sizeof(int) * s->size);
    56. int i = 0;
    57. int j = -1;
    58. next[i] = j;
    59. while (i < s->size - 1) {
    60. if (j == -1 || s->data[i] == s->data[j]) {
    61. i++;
    62. j++;
    63. next[i] = j;
    64. }
    65. else {
    66. j = next[j];
    67. }
    68. }
    69. return next;
    70. }
    71. // 输出 next 数组
    72. void printNext(int* next, int len) {
    73. for (int i = 0; i < len; i++) {
    74. printf(i == 0 ? "%d" : "->%d", next[i] + 1);
    75. }
    76. printf("\n");
    77. }
    78. // kmp 算法
    79. void kmpMatch(String* master, String* sub, int* next) {
    80. int i = 0;
    81. int j = 0;
    82. while (i < master->size && j < sub->size) {
    83. if (j == -1 || master->data[i] == sub->data[j]) {
    84. i++;
    85. j++;
    86. }
    87. else {
    88. j = next[j];
    89. }
    90. }
    91. if (j == sub->size) {
    92. printf("匹配成功");
    93. }
    94. else {
    95. printf("未匹配成功");
    96. }
    97. }
    98. int main() {
    99. String* s1 = initString();
    100. stringAssign(s1, "ABACCABABD");
    101. String* s2 = initString();
    102. stringAssign(s2, "CCAB");
    103. int* next = getNext(s2);
    104. printNext(next, s2->size);
    105. kmpMatch(s1, s2, next);
    106. return 0;
    107. }

    运行结果:

     kmp 是一种高效的模式匹配算法,他牺牲了一定的空间去保存 next 数组。 

  • 相关阅读:
    WebGPT VS WebGPU
    Git撤销本地commit(转)
    mongodb数据库操作
    【FI】FB02中Coding Block字段如何设置为可修改
    电子签名-为你的数据签字画押
    博弈论
    构建你的Conda包:使用conda skeleton命令打造包的骨架
    MySQL中的行锁
    聊聊Adapter模式
    13.56M系列芯片-SI522/SI522A/SI523/ FMI7522 /FMI7550 /RC522芯片常见问题解答
  • 原文地址:https://blog.csdn.net/xuan3215/article/details/127607173