408数据结构学习笔记——串、朴素模式匹配、kmp算法及其改进

简介: 408数据结构学习笔记——串、朴素模式匹配、kmp算法及其改进

1.串的定义(不在大纲范围)

串,即字符串( String)是由零个或多个字符组成的有限序列

子串:串中任意个连续的字符组成的子序列。

主串:包含子串的串。

字符在主串中的位置:字符在串中的序号。

子串在主串中的位置:子串的第一个字符在主串中的位置。

串是一种特殊的线性表,数据对象限定为字符集,基本操作(增删改查)对象为子串

0ce935f0e31f453ba561a676935b544b.png其中StrCompare(S, T)字符串比较操作实际上比较的是字符对应的二进制数的大小(ASCII码)

2.串的存储结构(不在大纲范围)

2.1.串的顺序存储

//静态数组方式
#define MAXLEN 255 //
typedef struct{
    char ch[MAXLEN];    //静态分配一个数组,数组每个元素存储一个字符
    int length;    //串的实际长度
}sString;
//动态分配方式,使用完后要手动free
typedef struct{
    char *ch;    //按串长度分配空间,ch指向串的首地址
    int length;    //串的实际长度
}hString;
hString str;
str.ch = (char*)malloc(MAXLEN * sizeof(char));
str.length = 0;

2.2.串的链式存储

typedef struct stringNode{
    //因为*next指针的大小为4,如果ch元素的个数过低,则存储密度就低
    char ch[4]; 
    struct stringNode *next;
}stringNode, *string;

3.朴素模式匹配

542a89e5a6f240839c0bca635fc89873.png

算法思想:从S的首个字符开始选取与T相同长度的字符内容,逐一与T的字符串内容进行匹配。如果有任意一个字符不同,则进行下一轮匹配;如果都相同,则结束循环,返回进入循环的S的字符的地址

最坏时间复杂度:O(mn)

int index(sString S, sString T){
    int k = 1;
    int i = k, j = 1;
    //遍历S和T
    while (i <= S.length && j <= T.length){
        //当前s和t的字符相同,则继续匹配
        if (S.ch[i] == T.ch[j]){
            i++;
            j++;
        }
        //当前s和t的字符不相同,则k向后移动,从k处继续下一轮循环
        else{
            k++;
            i = k;
            j = 1;
        }
    }
    //s和t相同,则返回k
    if (j > T.length) return k;
    else return 0;
}

缺点:没有考虑到子串和主串的在匹配中可能含有公共前缀,需要经常回溯

4.kmp算法(选择题)

改进思路:

1.主串指针不回溯,只有模式串指针回溯

2.当j = k时,匹配失败,说明前k - 1个元素是匹配成功的,因此,可以将模式串指针(主串指针不动)移动到下一个已经匹配成功部分的模式串(子串)中的最大公共前后缀的位置上

串的前缀:包括第一个字符,且不包括最后一个字符的子串(即不包括自身)

串的后缀:包含最后一个字符,且不包含第一个字符的子串(即不包括自身)

next数组:当j = k时,匹配失败,前k - 1个字符组成的字符串记为S,next[j] = S的最长相等前后缀长度+1(S自身比较)。特别的,next[1] = 0,next[2] = 1(任何模式串)

1.next[1] = 0的含义是设主串的匹配指针为i,模式串的匹配指针为j,next[1]出现的场合是模式串和主串的第一次匹配就失败,此时,执行操作:j = next[j] 即j = next[1] = 0,执行完后执行i++,j++就可以直接重新对主串的下一个元素和模式串的第一个元素进行匹配

2.next[2]的求法是:设字符串为abab,j = 2,即需要求a的最长相等前缀后缀,因为前缀不包含最后一个字符,后缀不包含第一个字符,因此,最长相等前后缀为0,next[2] = a的最长相等前后缀长度 + 1 = 0 + 1 = 1

3.为什么next[j] = S的最长相等前后缀长度+1中,需要+1:如果不+1,仅仅是移动到最长相等前后缀的最后一个元素,而我们需要进行匹配的是下一个元素

例:主串:ABABBA 模式串:ABAA    i为主串工作指针,j为模式串工作指针

当i = 4,j = 4时,匹配失败,模式串的最长相等前后缀长度为1,如果不加1,j = next[j] = 1,这样就会回到A,而按照kmp算法的思想A不需要再次进行匹配,我们需要进行的下次匹配是模式串的B

时间复杂度O(m+n)

5.kmp算法的改进

d627c9ce01ca4f0ba597333d11e31fb5.png

以王道书图中的例子为例。

  1. j = 4 指向a,i = 4 指向b时,匹配失败,则 j = next[4] = 3
  2. 进行下一轮匹配:j = 3 指向a,i = 4 指向b,匹配失败,则 j = next[3] = 2
  3. 进行下一轮匹配:j = 2 指向a,i = 4 指向b,匹配失败,则 j = next[2] = 1
  4. 进行下一轮匹配:j = 1 指向a,i = 4 指向b,匹配失败,则 j = next[1] = 0
  5. 进行下一轮匹配:j++,i++,j = 1 指向a,i = 5 指向a

从中可知,2 - 5的匹配结果其实是已知的,因为在 1 的时候就已经进行过一次模式串为a,主串为b的匹配,因此,2 - 5的匹配实际上是可以进行优化的

改进方法:在next数组的基础上,如果出现匹配失败的情况,需要多进行一次 j 指向的模式串元素是否与更新后 j = next[j] 指向的元素相等的判断,如果两者相等,则继续递归进行 j = next[next[j]],直至两者不相等为止,然后将结果更新为nextval数组

6.王道课后题

9988172c280f4902afd894fc95840003.png

  1. next[1]出现在模式串的第一个元素与主串当前元素匹配失败的情况下,将模式串的工作指针归零,然后进行j++和i++操作,从头开始匹配模式串和主串的下一个元素
  2. 取模式串中最大相等前后缀的长度作为模式串右移的距离,k的最大值是j - 1,因为前缀不包括最后一个字符,后缀不包括第一个字符
  3. 其他情况是模式串的最大相等前后缀仅为第一个字符和最后一个字符,取next[j]  = 1就是直接 j 移动到模式串的最后一个字符的当前位置,模式串从第一个位置,主串从第i个位置进行匹配

9a76211ce2344c688006110c1666e09d.png

解:除了next[1]不固定+1外(需要从头开始匹配模式串和子串),所有next数组都要固定 + 1,表示从模式串和子串的公共前后缀的下一个字符开始匹配(公共前后缀的意义就是将模式串某些前缀和后缀相同,在公共前后缀的前缀+1处匹配失败后,不需要匹配,直接快速移动到公共前后缀的后缀+1处进行下次匹配)

  1. j = a, next[1] = 0(next[1]从头开始匹配模式串和子串,固定为0)
  2. j = a a, next[2] = 0 + 1 = 1(前缀不能包括最后一个字符,后缀不能包括第一个字符)
  3. j = aa b, next[3] = 1 + 1 = 2(公共前后缀长度为1)
  4. j = aab a, next[4] = 0 + 1 = 1(第一个字符为a,最后一个字符为b,没有公共前后缀)
  5. j = aaba a, next[5] = 1 + 1 = 2
  6. j = aabaa c, next[6] = 2 + 1 = 3

2.解:next[j]的j代表取模式串的前j个字符计算公共前后缀

  1. i = 1 = a, j = 1 = a, 匹配成功 i++, j++
  2. i = 2 = a, j = 2 = a, 匹配成功 i++, j++
  3. i = 3 = b, j = 3 = b, 匹配成功 i++, j++
  4. i = 4 = a, j = 4 = a, 匹配成功 i++, j++
  5. i = 5 = a, j = 5 = a, 匹配成功 i++, j++
  6. i = 6 = b, j = 6 = c, 匹配失败 j = next[j] = 3
  7. i = 6 = b, j = 3 = b, 匹配成功 i++, j++
  8. i = 7 = a, j = 4 = a, 匹配成功 i++, j++
  9. i = 8 = a, j = 5 = a, 匹配成功 i++, j++
  10. i = 9 = b, j = 6 = c, 匹配失败 j = next[j] = 3
  11. i = 9 = b, j = 3 = b, 匹配成功 i++, j++
  12. i = 10 = a, j = 4 = a, 匹配成功 i++, j++
  13. i = 11 = a, j = 5 = a, 匹配成功 i++, j++
  14. i = 12 = c, j = 6 = c, 匹配成功 i++, j++
  15. i > 模式串长度 结束循环 return true


相关文章
|
21天前
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
21 3
|
1天前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
13天前
|
机器学习/深度学习 算法 BI
机器学习笔记(一) 感知机算法 之 原理篇
机器学习笔记(一) 感知机算法 之 原理篇
|
1天前
|
算法 Java
KMP算法详解及其在字符串匹配中的应用
KMP算法详解及其在字符串匹配中的应用
|
3天前
|
人工智能 算法 BI
一篇文章讲明白KMP算法(俗称看毛片算法)
一篇文章讲明白KMP算法(俗称看毛片算法)
|
9天前
|
算法 Java
Java数据结构与算法:字符串匹配算法之KMP算法
Java数据结构与算法:字符串匹配算法之KMP算法
|
8天前
|
算法 C语言
【数据结构与算法 经典例题】使用栈实现队列(图文详解)
【数据结构与算法 经典例题】使用栈实现队列(图文详解)
|
2天前
|
存储 缓存 算法
堆和栈的区别及应用场景
堆和栈的区别及应用场景
|
8天前
|
存储 测试技术
【数据结构】操作受限的线性表,栈的具体实现
【数据结构】操作受限的线性表,栈的具体实现
17 5
|
8天前
|
算法 C语言
【数据结构与算法 经典例题】使用队列实现栈(图文详解)
【数据结构与算法 经典例题】使用队列实现栈(图文详解)