又见 “KMP”

同 http://blog.csdn.net/shanshanpt/article/details/8679459 ,都是回忆版


>: KMP的一些概念介绍就不用了,网上到处都是,此处只是为了整理,仅此而已


>: 网上看到的文章都是从 “ next ”数组和i,j指针的移动来讲的,这里也一样,不过不同的是,想说的更清晰一点!


 >: 传统的BF算法与KMP有什么区别呢?

      BF: 原串: B A B A B ...

               子串:  B A B A D     此时匹配不上了,那么它又会从下一个字符开始:   原串: A B A B ....

----------------------------------------------------------------------------------------------------------子串:     A B A D -----------------------------------------------------------------------------------------------------


     然后再接着找,那么这样我们前期的一些匹配就浪费了,而KMP就是尽最大的可能去利用已经做过的工作!

     

     KMP: 原串: B A B A B A D A A ....

                  子串: B A B A  D .      

      不好,发现到不匹配的,怎么办呢,此时KMP不想BF那么傻,从头再开始,而是-------> 对于前面的已经字符串啊,它总是想找到一个分界( 其实就是一个长度m ),能够使得什么呢,能使得最前面的m长度字串( 从前面往后数m个char )和最后面的m个长度字串( 从后往前数m个char,注意此处说的是前面已经匹配成功的一部分哦~^_^ )是相等的!那么变成什么呢?

                原串:B A B A B A D A A ....

                子串:       B A B A D     那么我们可以看到 下面的连个B A  其实就是前面已经匹配过的字串相等的情况( 子不过此处有点特殊,下面再看个实例 )

                                                         因为我们知道之前是字串中是的D不匹配,当我们找到头部m长度字串和尾部m长度字串相等的时候,那么此时头部m长度

                                                         移动到原来尾部m长度处,那么与原串中的相应的m长度必然是匹配的!这就是Kmp不同于BF的关键之处!

               再看一个实例:

               原串: B A C B A  B A D A A ....

               子串: B A C B A  D .

               KMP处理后变成:

                    原串:B A C B A B A D A A ....

                    子串:           B A C B A D       红色 B 和 D是之前不匹配处,绿色的三个 B A 就是上面KMP处理的结果!


      >:  讲了这么多,大家都可以认为是废话哦,呵呵,下面就是重要的next数组求解:使用的是递推法!

    >: 我们一般定义next[0] = -1,假设 next[j] = k,  即 M[ 0...k - 1 ] == M[ j - k... j - 1 ];

     >: 然后接着往下扫描,若M[j] == M[k],则有M[ 0..k ] == M[ j - k , j ],很显然,next[ j + 1 ] = next[ j ],而next[j] == k,那么有next[ j + 1 ] = k + 1; 

    >:                           若M[j] != M[k], 匹配失败的时候,k = next[k] ( 书上是这么写的,可是为什么呢? ) 我们可以好好想想,上面M[j] == M[k]时候,直接+1

    就能够得到我的next,此处不相等,那么我们可以从M前面串的字串来处理,也就是说有可能从 M[ next[k] ]

下面写下代码:

#include <stdio.h>
#include <string.h>

void get_next( char *tmp,int *next )
{
    int j,k;
    int len = strlen( tmp );

    next[0] = -1;                            // 初始化 
    j = 0;
    k = -1;

    while( j < len - 1 )
    {
        if( k == -1 || tmp[j] == tmp[k] )    // tmp[j]==tmp[k]
        {
			j++;
			k++;

            if( tmp[j] == tmp[k] )	   // if相等,那么和前面的next是一样
	    {                               
                next[j] = next[k];
	    }
            else
	    {
                next[j] = k;                
	    }
        }
        else                   
	{
            k = next[k];                    // 长串不匹配,那么可以看看子串有没有匹配的! 
	}
    }
}

int KMP( char *s,char *tmp, int next[] )
{
    int i = 0,j = 0, len_s = strlen( s ), len_p = strlen( tmp );

    while( i < len_s )
    {
        if( j == -1 || s[i] == tmp[j] )  // 刚刚进来 || 此个char匹配上 
        {
            i++;
            j++;
        }
        else
        {
            j = next[j];       // 消除了指针i的回溯( 不同于BF的地方 )
        }

        if( j == len_p )       // 匹配结束 
	{
            return i - len_p;  // 返回匹配位置
	}
    }

    return -1;                 // 没有找到
}



int main()
{
	char str[80], sub_str[20];
	int  next[100], first_at;

	while( gets( str ) )
	{
		gets( sub_str );
		
		get_next( sub_str,next );             // 递推法求next数组 
		first_at = KMP( str, sub_str, next ); //  

		if( first_at != -1 )
		{
			printf("At: %d\n", first_at);
		}
		else
		{
			printf("No cmp...\n");
		}
	}

	return 0;
}



 

KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,通过预处理模式串(Pattern)构建**部分匹配表(Partial Match Table,又称`next`数组)**,在匹配失败时利用已匹配的信息跳过不必要的比较,将时间复杂度从暴力匹配的O(m*n)优化到O(m+n)。以下是详细过程: --- ### **1. 核心思想** - **避免重复比较**:当主串(Text)与模式串不匹配时,利用`next`数组跳过已匹配的部分前缀,直接从可能匹配的位置继续比较。 - **部分匹配表(`next`数组)**:记录模式串中每个位置的最长**相同前后缀长度**,用于指导跳转。 --- ### **2. 构建`next`数组** #### **定义** - `next[j]`表示模式串`P[0..j]`的最长相同前后缀长度(前缀不包含最后一个字符,后缀不包含第一个字符)。 - 例如,模式串`"abab"`的`next`数组为`[-1, 0, 0, 1]`(具体构建过程下文)。 #### **构建步骤** 1. 初始化: - `next[0] = -1`(约定,表示无匹配)。 - 定义两个指针`i = 0`(后缀末尾)、`j = -1`(前缀末尾)。 2. 遍历模式串(从第2个字符开始): - 若`P[i] == P[j]`: - `next[++i] = ++j`(当前字符匹配,最长前后缀长度+1)。 - 否则: - 若`j != -1`,回退`j = next[j]`(利用已计算的部分匹配信息)。 - 若`j == -1`,`next[++i] = 0`(无匹配,重置为0)。 #### **示例:构建`"abab"`的`next`数组** | 步骤 | i | j | P[i] | P[j] | 操作 | next数组 | |------|---|---|------|------|--------------------------|----------------| | 初始 | 0 | -1| - | - | next[0] = -1 | [-1] | | 1 | 1 | 0 | a | a | P[1]==P[0] → next[1]=0 | [-1, 0] | | 2 | 2 | 0 | b | a | P[2]!=P[0] → j=next[0]=-1 | [-1, 0, 0] | | 3 | 2 | -1| b | - | j=-1 → next[2]=0 | [-1, 0, 0] | | 4 | 3 | 0 | a | a | P[3]==P[0] → next[3]=1 | [-1, 0, 0, 1] | --- ### **3. KMP匹配过程** 1. **初始化**: - 主串指针`i = 0`,模式串指针`j = 0`。 2. **遍历主串**: - 若`T[i] == P[j]`: - `i++`, `j++`(继续匹配下一个字符)。 - 否则: - 若`j != 0`,`j = next[j]`(利用`next`数组跳转)。 - 若`j == 0`,`i++`(模式串第一个字符不匹配,主串后移)。 3. **匹配成功**: - 当`j == m`(模式串长度)时,说明找到匹配,返回起始位置`i - j`。 #### **示例:主串`"abababc"`匹配模式串`"abab"`** | 步骤 | i | j | T[i] | P[j] | 操作 | 状态 | |------|---|---|------|------|--------------------------|--------------------| | 初始 | 0 | 0 | a | a | T[0]==P[0] → i=1,j=1 | ab[0] vs ab[0] | | 1 | 1 | 1 | b | b | T[1]==P[1] → i=2,j=2 | abab[0] vs abab[0] | | 2 | 2 | 2 | a | a | T[2]==P[2] → i=3,j=3 | ababa vs ababa | | 3 | 3 | 3 | b | b | T[3]==P[3] → i=4,j=4 | 匹配成功(j=4=m) | **匹配结果**:主串中从索引0开始匹配成功。 --- ### **4. 代码实现(C语言)** ```c #include <stdio.h> #include <string.h> // 构建next数组 void buildNext(const char *P, int *next) { int m = strlen(P); next[0] = -1; int i = 0, j = -1; while (i < m - 1) { if (j == -1 || P[i] == P[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } // KMP匹配 int KMP(const char *T, const char *P) { int n = strlen(T), m = strlen(P); int next[m + 1]; buildNext(P, next); int i = 0, j = 0; while (i < n && j < m) { if (j == -1 || T[i] == P[j]) { i++; j++; } else { j = next[j]; } } return j == m ? i - j : -1; // 返回匹配起始索引或-1 } int main() { const char *T = "abababc"; const char *P = "abab"; int pos = KMP(T, P); printf("匹配位置:%d\n", pos); // 输出:0 return 0; } ``` --- ### **5. 关键点总结** - **`next`数组的作用**:记录模式串中每个位置的最长相同前后缀长度,用于跳过不必要的比较。 - **时间复杂度**: - 预处理`next`数组:O(m)。 - 匹配过程:O(n)。 - 总复杂度:O(m + n)。 - **空间复杂度**:O(m)(存储`next`数组)。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值