从C++strStr到字符串匹配算法

字符串的匹配先定义两个名词:模式串和文本串。我们的任务就是在文本串中找到模式串第一次出现的位置,如果找到就返回位置的下标,如果没有找到返回-1.其实这就是C++语言里面的一个函数:

extern char *strstr(char *str1, const char *str2);

对于这个函数的解释:

str1: 被查找目标
str2: 要查找对象
返回值:如果str2是str1的子串,则返回str2在str1的首次出现的地址;
		如果str2不是str1的子串,则返回NULL。
例如:
char str[]="1234xyz";
char *str1=strstr(str,"34");
cout << str1 << endl;
显示的是: 34xyz

  返回值是一个指针,这个指针指向文本串中第一次出现模式串的位置。


 字符串查找的暴力算法

先看LeetCode上的一道题目,实现这个函数 int strStr(string haystack, string needle); ,要求返回文本串中出现模式串的下标值。

1.如果模式串为NULL,那么直接返回0.
2.如果模式串的长度大于文本串,那么一定查找不到,返回-1.
3.如果存在的话,查找的范围可以限定在文本串的0~s.size()-p.size();

所以暴力算法的代码实现:

int strStr(string haystack, string needle) 
{
	int i = 0;
	//模式串为空
	if(needle.empty())
	{
		return 0;
	}
	//文本串的大小小于模式串
	if(haystack.size() < needle.size())
	{
		return -1;
	}
	//确定查找的范围
	for(i = 0; i <= haystack.size()-needle.size(); ++i)
	{
		int j = 0;
		for(j = 0; j < needle.size(); ++j)
		{
			if(haystack[i+j] != needle[j])
			{
				break;
			}
		}//for
		if(j == needle.size())
		{
			return i;
		}
	}//for
	if(i == haystack.size()-needle.size() + 1)
	{
		return -1;
	}
}

字符串查找的KMP算法

上面的暴力算法,在查找失败以后都要进行回溯,下面再给出一个版本,明显的看到i,j的回溯:

int strStr(string haystack, string needle) 
{
	int sLen = haystack.size();
	int pLen = needle.size();
	
	int i = 0;
	int j = 0;
	while(i < sLen && j < pLen)
	{
		if(haystack[i] == needle[j])
		{
			++i;
			++j;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}//while
	
	if(j == pLen)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

假设我们已经知道了KMP的next数组,所以每次失配以后,i不回溯,j回溯到next[j]指定的位置。也就是 j = next[j]; 。

int KmpSearch(char *s, char *p)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
 
    while(i < sLen && j < pLen)
    {
        //如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
        if(j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]     
            //next[j]即为j所对应的next值
            j = next[j];
        }
    }//while
 
    if(j == pLen)
    {
        return i - j;
    }
    else
    {
        return -1;
    }
}

对于上面的j=-1和next[0]=-1作下面的解释:

说完了KMP的大的框架,下面就得说一下next数组的求解过程了:  

void GetNext(char *p, int *next)
{
    int pLen = strlen(p);
    int j = 0;
    int k = -1;
    next[0] = -1;
 
    while(j < pLen - 1)
    {
        //p[k]表示前缀,p[j]表示后缀
        if(k == -1 || p[j] == p[k])
        {
            ++k;
            ++j;
            next[j] = k;//表示在j这个字符之前,能够构成公共前后缀的最大字符数
        }
        else
        {
            k = next[k];//回溯之前已经有过匹配的前缀
        }
    }
}

  

KMP算法的一个改进  

上面的KMP算法已经能够很好的跑出结果来了,但是还可以改进,看下面的一个字符串的匹配:

改进的代码实现:

void GetNextVal(char *p, int *next)
{
    int pLen = strlen(p);
    int j = 0;
    int k = -1;
    next[0] = -1;
 
    while(j < pLen - 1)
    {
        //p[k]表示前缀,p[j]表示后缀
        if(k == -1 || p[j] == p[k])
        {
            ++k;
            ++j;
            if(p[j] != p[k])
            {
                next[j] = k;//表示在j这个字符之前,能够构成公共前后缀的最大字符数
            }
            else
            {
                next[j] = next[k];//因为不能出现p[j] = p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            }
                 
        }
        else
        {
            k = next[k];//回溯之前已经有过匹配的前缀
        }
    }
}

再来看一个极端的情况:

  

posted @ 2015-10-01 20:40  stemon  阅读(1364)  评论(0编辑  收藏  举报