zoukankan      html  css  js  c++  java
  • 看正月点灯笼老师的笔记—KMP

    kmp代码:

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define N 100
    char text[N], pattern[N];
    void move(int prefix[], int n)   // 对 prefix 表 进一步处理
    {
    	for (int i = n - 1; i > 0; i--)   // 整体后移
    		prefix[i] = prefix[i - 1];
    	prefix[0] = -1;
    
    	/*for (int i = 0; i < n; i++)
    	{
    	printf("%d ", prefix[i]);
    	}puts("");*/
    }
    void prefix_table(int prefix[], int n)  // 打印 prefix 表
    {
    	prefix[0] = 0;   // 只有一位没有前后缀
    	int len = 0;
    	int i = 1;
    	while (i < n)   // 用 while 不用 for, 可以同时实现 求所有子串的最缀 及当前子串的多次查找最缀
    	{
    		if (pattern[i] == pattern[len])
    		{
    			len++;
    			prefix[i++] = len;
    		}
    		else if (len > 0)
    			len = prefix[len - 1];  // 当前子串的 最缀 还没找到,再次循环
    		else
    			prefix[i++] = len;  //此时 len==0 
    	}
    	move(prefix, n);
    }
    void kmp_search()
    {
    	int n = strlen(pattern);
    	int m = strlen(text);
    	int *prefix = (int*)malloc(sizeof(int)*n);
    	prefix_table(prefix, n);
    
    	// pattern 用 j	, text 用 i
    	int i = 0, j = 0, ci = 0;
    	while (i < m&&j < n)
    	{
    		if (j == n - 1 && text[i] == pattern[j])  // j==n-1 对应 j 移动到 pattern 的最后一位,如果这一位仍然相等的成功匹配了
    		{
    			printf("找到了第 %d 个 ,起始位置在 %d 位置
    ", ++ci, i - j);
    			j = prefix[j];
    			i++;
    			continue;
    		}
    		if (text[i] == pattern[j])
    			i++, j++;
    		else
    			j = prefix[j];     //这句是精髓啊
    		if (j == -1)     // -1是为了 这种情况可以特殊处理
    			j++, i++;
    	}
    }
    int main(void)
    {
    	printf("请输入要找的主串text: ");
    	scanf("%s", text);
    	printf("请输入要找的模式串pattern: ");
    	scanf("%s", pattern);
    
    	kmp_search();
    
    	system("pause");;
    	return 0;
    }
    

      

     

    一,关于最长公共前后缀的理解

    1,首先最。。不能包含自身

    所以    prefix[0]=0

    2, 虽然字符串 aba 是对称的,但是其最长公共前后缀为 a

    因为 若其最长公共前后缀长度为2的话,则前缀为 ab,后缀为 ba ,两者不同

    3,prefix 要增加就只能是一次加 1 的递增,

    因为 前缀始终是不变的,变的是后缀,且后缀也只是每次 加上一个字符,

    所以最长公共前后缀最多也只能一次加 1 

    4,len 

    len 指向  ((当前字符串去掉最后一位的字符串) 的最长公共前后缀) 的最后一个位置 在加一位

    为什么指向这里呢? 看一下例子就能 明白了。

    对于 ABAAB ,

    当前字符串去掉最后一位的字符串 为 ABAA

    (当前字符串去掉最后一位的字符串) 的最长公共前后缀 为 A

    ((当前字符串去掉最后一位的字符串) 的最长公共前后缀) 的最后一个位置 在加一位 为 第一个B 的位置

    对于 ABAAB 这个字符串 ,已知 ABAA 的最长公共前后缀为 A,长度为 1,

    ABAAB 要想在 ABAA 的基础上,让最长公共前后缀增加,只能是 比较 第五个字符 与 第一个B

    5,接下来就是我自己的 彼得一激灵,即对 len=prefix[len-1] 和 j=prefix[j] 的理解:

    这两句的原理是一样的,都是根据前面子串的最长公共前后缀 去 减少匹配数次,

    至于 j  为什么不用减 1,因为 prefix_table 整个往后移了一位。

       设 A 为最长公共前后缀  ,B为不相干字符串,a为最长公共前后缀的最长公共前后缀,  b,c 为不相干字符串

     已知 ABA 的最长公共前后缀已经算好了,为 A ,接下来要算 ABAx 的:

    首先 比较 B 与 x ,若相等,则长度可知。

                                 若不相等,由于原长度是在 a2 位置,但应退回 a1 置,即最长公共前后缀的最长公共前后缀, 而不是 c 。

                 因为此时 ABA 的第二长公共前后缀是 a1 和 a4 ,即使 x==c,也无用。

    另外,附上我自己走一遍的流程图:

     三,这是 kmp_search 函数的流程图

    三,move 函数(比较 两次函数两次匹配)

    因为每次无法匹配时,都要找前一个子串的最长公共前后缀,且为了考虑前一个子串的所有前后缀也都匹配不上的情,

    且 pattern 的最长公共 前后缀 我们无需关心,因为这一位 匹配成功 的就结束了,匹配不成功的话 关心的是前一个子串的最长公共前后缀,

     所以,为了写代码的方便,我们直接让前缀表后移一位,第一位的 -1,当成 是 前一个子串的所有前后缀也都匹配不上的情况。

    END

    ============ ======== ======== ======= ====== ===== ==== === == =

     路还是要自己走过才知道。

    The road or to know through their own.

  • 相关阅读:
    CI框架主题切换的功能
    centos7 编译安装 php7.4
    单用户登陆demo-后者挤到前者,类似QQ
    nginx 负载均衡的配置
    PHP计算每月几周,每周的开始结束日期
    Centos7 编译安装PHP7
    TP 3.2.3 接入PHPMailer
    外部js引用vue实例环境的方式
    linux常用命令
    计算机中的二级制
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/12363701.html
Copyright © 2011-2022 走看看