KMP算法,next数组详解(c++)

目录

1.BF算法原理

2.KMP算法原理

3.next数组原理


1.BF算法原理

BF算法本质是上是双重for循环。只是用一个循环来实现的通过j=i-j+1;的方式将主串的下标从"头"开始遍历。

代码如下:

int BF(const char* S, const char* T) 
{
    if (S == NULL || T == NULL) 
    {
        return -1;
    }
    int lenstr = strlen(S);
    int lensub = strlen(T);
    int i = 0;
    int j = 0;
    while (i < lenstr && j < lensub) 
    {
        if (S[i] == T[j]) 
        {
            i++;
            j++;
        }
        else 
        {
            i = i - j + 1;//利用这个公式将i回退到对应的位置
            j = 0;		 
        }
    }
    if (j >= lensub)   
    { 
        return i - j;
    }
    else
        return -1;

}                 

2.KMP算法原理

假设dadstr是主字符串,sonstr是子字符串,这两者是要比较的子字符串是否在主字符串中的字符串。如果当dadstr[i]!=sonstr[j]时,按照BF算法i时要回退到暴力for循环第几次开始对应的位置的,但是如果sonstr字符串是局部头尾对称的呢,比如sonstr是:abcabf我们可以知道当f与主字符串不相等时,f之前的字符串是头尾对称的,此时我们还需要将i回退吗,不需要因为既然能到f位置那么f之前的位置和主字符串肯定相等,并且abcab是对称的,我们就没必要i回退了,我们只需要将j回退到c位置再开始就是下一个比较的位置了。

换一种理解方式,假设有主字符串aweq和子字符串weq。我们可以这样去比较,如果if dadstr[i]==sonstr[j];i++,j++; if dadstr[i]!=sonstr[j] && dadstr[i]!=sonstr[0] ;j=0,i++; else j=0;这一串代码的意思就是i去遍历主字符串,j去遍历子字符串,如果 i位置的主字符串和j位置的字字符串相等那么就继续往后面遍历,如果不相等i无需要回退,我直接将j回退到头重新比较。这样的比较也是符合逻辑的,但是如果是这样的两对字符串呢? ->主字符串acbabcabcef和子字符串abcabcef我们发现我们刚刚的那个代码无法得到正确答案,为什么呢?因为如果按照刚刚那个算法j直接回退到0,但是我们发现在这种情况中,我们直接回退到0位置是错了,直接回退到0位置会导致少了一部分字符串比较而这一个字符串刚刚好是我们的答案,所以结果错了。所以我们要在此算法的基础上去讨论将j回退到什么位置才行。这就是next数组出现的理由,因为对称所以next数组就决定了j回退到正确的位置。

代码如下:

int strStr(string dadstr, string sonstr)
{
	int* next = new int[sonstr.size()];
	memset(next,0, sonstr.size()*4);
	GetNext(next, sonstr);

	int len1 = dadstr.size(); //计算出主串的长度
	int len2 = sonstr.size();   //计算出子串的长度


	if (len1 == 0 && len2 != 0) return -1;
	if (len1 == 0 && len2 == 0) return 0;
	if (len1 != 0 && len2 == 0) return 0;

	int j = 0;//主串的下标
	int i = 0;//子串的下标
    
	while (i < len1 && j < len2)
	{
		如果主串和子串的字符相同或者j回退到了-1位置,我们都要把i和j进行++
		if ((j == -1) ||dadstr[i] == sonstr[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];//主串和子串的字符不匹配,j需要依靠next数组进行回退操作
		}
	}

	if (j >= len2) return i - j;//子串遍历完了
	else return -1;//子串没有遍历完了
}

3.next数组原理

接上面所说的KMP算法原理中next数组的由来,这里讨论怎么得出next数组。next数组中next[i]是指的到i位置时i之前的字符串中尾部是否与前面具有对称性,且是第几个对称。所以我们只需要判断sonstr[i]是否满足对称即可。

例如:abcabf 对应的next数组中的元素应该是 0 0 0 1 2 0 。

i=0时,"a"字符串中无头尾对称(一个字符不算)所以next[0]=0;

i=1时,"ab"字符串中也无头尾对称,所以next[1]=0;

i=2时,"abc"字符串中也无头尾对称,所以next[2]=0;

i=3时,"abca"字符串中尾部'a'与头部'a'对称且是第一个对称,所以next[3]=1;

i=4时,"abcab"字符串中尾部"ab"与头部"ab"对称且'b'是第二个对称,所以next[4]=2;

i=5时,"abcabf"字符串中无头尾对称,所以next[5]=0;

那么根据next数组的组成原理,我们可以写出这样的代码。

//方法一:
void Getnext(int* next, const string& t) {
	int i = 0;
	next[0] = -1;
	int j = -1;
	while(i < t.length) {
		if(j == -1||t.ch[i] == t.ch[j]) {
			i++;
			j++;
			next[i] = j;
		} else {
			j = next[j];
		}
	}
}

这就是next数组的代码,但是根据next数组的定义"next数组中next[i]是指的到i位置时的i前面字符串中尾部是否与前面具有对称性,且是第几个对称。"next[i]是到i位置时i前面的字符串的特性,这里还可以写成到i位置时包括i字符串的特性。此时创建next数组代码还可以写成这样:

//方法二:
void GetNext(int* next, const string& s)
{
	int size = 1;
	for(;size < s.size();size++)
	{
		if (next[size-1] == 0)
		{
			if (s[size] == s[0])
				next[size] = 1;
			else
				next[size] = 0;
		}
		else
		{
			if (s[size] == s[next[size - 1]])
				next[size] = next[size - 1]+1;
			else
				next[size] = 0;
		}
	}
	next[0] = -1;
}

同理不同的next数组所写的KMP算法不一样。这里以教材书上为准也就是方法一的代码。

如有错误欢迎指出。

相关推荐
小屁孩大帅-杨一凡27 分钟前
java后端请求想接收多个对象入参的数据
java·开发语言
m0_6569747432 分钟前
C#中的集合类及其使用
开发语言·c#
java1234_小锋34 分钟前
使用 RabbitMQ 有什么好处?
java·开发语言
wjs202443 分钟前
R 数据框
开发语言
幺零九零零1 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
肘击鸣的百k路1 小时前
Java 代理模式详解
java·开发语言·代理模式
捕鲸叉1 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点1 小时前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
真的想不出名儿1 小时前
Java基础——反射
java·开发语言
努力编程的阿伟2 小时前
【Java SE语法】抽象类(abstract class)和接口(interface)有什么异同?
java·开发语言