子串问题(BF算法、KMP算法)

一、问题描述

假设字符串A: "abcababcabc"

       字符串B:"abcabc"

因为字符串A中有一部分和B相同,则称字符串B为字符串A的子串

二、BF算法

用i 和 j 分别表示字符串AB的下标,初始值都为0。

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
  i                    
字符串B a b c a b c          
  j                    

1、首先字符串A从头开始,判断往后strlen(B)个字符串是否和字符串B相等;

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i          
字符串B a b c a b c          
            j          

2、当走到下标为5的时候,字符串A B失配,则说明字符串A从下标为0开始的子串与B不相同,既然从下标为0开始没有,那就i回退到1(即回退了i - j + 1),j回退到0,再次进行匹配

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
    i                  
字符串B a b c a b c          
  j                    

3、当i为1,j为0是还是失配,i接着回退i - j + 1 = 2,j接着回退到0,接着匹配

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
      i                
字符串B a b c a b c          
  j                    

4、重复上述步骤,直到:i = 5,j = 0的时候

用i 和 j 分别表示字符串AB的下标,初始值都为0。

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i          
字符串B a b c a b c          
  j                    

此时,可以直到 j 走到字符串B末尾,字符串A B都匹配,那么说明从 i 开始存在子串B。

5、当i走到字符串A末尾都没有出现j走到字符串B末尾的情况说明字符串B不是字符串A的子串

代码:

# include
# include
# include

int BF(char *s, char *p, int pos)
{
	assert(s != NULL && p != NULL);

	int lens = strlen(s);
	int lenp = strlen(p);
	if (lenp > lens)
	{
		return -1;
	}

	int i = pos;
	int j = 0;

	while (i < lens && j < lenp)
	{
		if (s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}
	if (j >= lenp)
	{
		return i - j;
	}
	return -1;
}
int main()
{
	char *s = "abcababcabc";
	char *p = "abcabc";
	printf("%d\n", BF(s, p, 0));
	return 0;
}

三、KMP算法

1、KMP算法思路

                       i

A:a b c a b   a b c a b c

                       j

B:  a b c a b   c 

如上图所示,当在下标为i处失配。则存在:

B[0] ... B[k - 1]  == A[i - k] ... A[i - 1]

A[i - k] ... A[i - 1] == S[i - k] ... S[i - 1]

可推出:B[0] ... B[K - 1] == B[j - k] ... B[j - 1]

因此:j不必退回0,i也不必退回 处,只需j退回到字符k处,如下:

                       i

A:a b c a b   a b c a b c

                       j

         B:  a b   c a b   c 

然后:接着i 和 j处接着往下匹配即可,失配之后继续按照上述方法回退

2、next数组

引入next数组就是为了求出上述k的数值,next数组长度和字符串A长度相同。

next[i]存储的数值是:从A[0] - A[i]组成的字符串中两个相等真子串的长度,这两个满足以下条件

  • 第一个串以下标0开始
  • 第二个串以下标i - 1结束

例:字符串A: "abcababcabc"的next数组如下

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
next -1 0 0 0 1 2 1 2 3 4 5
真子串         "a" "ab" "a" "ab" "abc" "abca" "abcab"
void GetNext(const char *p, int *next)    //求next数组
{
	next[0] = -1;
	next[1] = 0;

	int j = 1;
	int k = 0;
	int lenp = strlen(p);

	while (j + 1 < lenp)
	{
                /*
                 *当k == -1 时,该位置对应的next应为0
                 *如果p[k] == p[j],则p[j+1] == k+1
                 */
		if (k == -1 || p[k] == p[j])
		{
			next[++j] = ++k;
		}
                /*
                 *当p[k] != p[j],则相当于子串匹配问题,用KMP算法思路,k回退,继续循环匹配
                 */
		else
		{
			k = next[k];
		}
	}
}

3、kmp算法流程

依然用i 和 j 分别表示字符串AB的下标,初始值都为0。

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
  i                    
字符串B a b c a b c          
  j                    

(1)跟BF匹配方式相同,第一次当i = j = 5时失配

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i          
字符串B a b c a b c          
            j          
next -1 0 0 0 1 2 1 2 3 4 5

此时,j没有必要一定退到0,只需回退到next[j]即可,而i可以不用回退,如下:

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i          
字符串B a b c a b c          
      j                
next -1 0 0 0 1 2 1 2 3 4 5

(2)然后判断B[j]是否和A[i]相等,如果相等i++,j++,很明显,此时不相等,那么j继续回退到next[j],如下:

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i          
字符串B a b c a b c          
  j                    
next -1 0 0 0 1 2 1 2 3 4 5

(3)此时B[j] == A[i],则i++,j++

下标 0 1 2 3 4 5 6 7 8 9 10
字符串A a b c a b a b c a b c
            i

j

       
字符串B a b c a b c          
   

j

                 
next -1 0 0 0 1 2 1 2 3 4 5

(4)重复上述步骤,直到 j 走到字符串B末尾,则说明从i - j处开始B串是A串的子串。如果i走到A串末尾 j 都没有走到B串末尾,则说明串B不是A串子串。

4、代码

int KMP(const char *s, const char *p, int pos)
{
	assert(s != NULL && p != NULL);

	int lens = strlen(s);
	int lenp = strlen(p);

	if (lens < lenp)
	{
		return -1;
	}

	int i = pos;
	int j = 0;
	int *next = (int *)malloc(sizeof(int) * lenp);
	assert(next != NULL);

	GetNext(p, next);  //O(m)

	while (i < lens && j < lenp)
	{
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}

	free(next);

	if (j >= lenp)
	{
		return i - j;
	}

	return -1;
}

 

 

你可能感兴趣的:(算法设计,数据结构)