字符串匹配算法有:暴力匹配、KMP
更正错误2019.12.08
KMP算法
先计算出要匹配的字符串的特征数值(字符串首尾相同的字符串最长长度 + 1),依据特征值指导字符跳转。
匹配函数如下:(其中p为查找串的指针,j为p下标,k为匹配的next值)
BM算法
BM算法和KMP算法的差别是对模式串的扫描方式自左至右变成自右至左。另一个差别是考虑正文中可能出现的字符在模式中的位置。这样做的好处是当正文中出现模式中没有的字符时就可以将模式大幅度滑过正文。
BM算法的关键是根据给定的模式W[1,m],,定义一个函数d: x->{1,2,…,m},这里x∈∑。函数d给出了正文中可能出现的字符在模式中的位置。
参考:https://www.cnblogs.com/huifeidezhuzai/p/9222366.html
KMP匹配算法
大致匹配流程
KMP依赖数据:
1、查找对象字符串的特征值分析,所谓的特征值就是字符出现频率高的字符串最长长度,以数字表示频率值;
2、源字符串、查找对象字符串;
公式:源字符串指针移动位数 = 目的字符串已匹配的字符数 - 目的字符串对应的部分匹配值
直接(暴力)匹配算法
//暴力匹配算法
char* strstr_cpy(char src[],char des[])
{
int length_src = strlen(src);
int length_des = strlen(des);
int i = 0;
int j = 0;
if(length_des <= 0 || length_src <= 0)
{
return 0;
}
while(i<length_src)/* 遍历源字符串 */
{
while(j<length_des)/* 遍历目标字符串 */
{
if(src[i+j] == des[j]) /* 如果当前位置的字符和目标字符相同,则持续往后移动 */
{
j++;
}
else /* 如果有任意一个字符不匹配,则重新开始 */
{
i++;
j = 0;
}
}
if(j >= length_des)
{
return src+i;
}
}
return NULL;
}
KMP算法实现与优化
/*
* * 函数功能:获取查找字符串的next值
* */
void kmp_get_next_index(char src[],int next[])
{
int m = strlen(src);
int i=0,j=0;
next[0] = 0; //字符串起始的next为0
for(i=1;i<m;i++)//遍历查找字符串
{
for(j=0;j<i;j++) //遍历第i个字符前的字串,以计算next值
{
if(!strncmp(src,src+i-j,j+1)) //判断字符串的相等个数:'P0 - Pj' = 'Pi-j - Pi'
{
next[i] = j+1; //除匹配情况外,next值都是1
}
}
}
}
/*
* * 函数功能:依据next值进行kmp字符匹配
* */
char* kmp_test(char src[],char des[],int next[])
{
int length_src = strlen(src);
int length_des = strlen(des);
int i = 0;
int j = 0;
int k=0;
if(length_des <= 0 || length_src <= 0)
{
return NULL;
}
while( i<length_src )/* 遍历源字符串 */
{
j=0;
while( j<length_des )/* 遍历目标字符串 */
{
if(src[i+j] == des[j]) /* 如果当前位置的字符和目标字符相同,则持续往后移动 */
{
j++;
}
else /* 如果有任意一个字符不匹配,则重新开始 */
{
if(0 == j)
i++;
else
i += j - next[j]; /* 源字符串依据next数组进行跳转 */
if(i<length_src)
break; /* 查找字符串从头开始重新匹配 */
else
return NULL;
}
kmp_countor++; /* 记录比较次数 */
}
if(j >= length_des && i < length_src) /* 如果已经比较完成,则返回查找字符串在源字符串的起始地址 */
{
return src+i; /* 返回查找字符串在源字符串中第一次出现的偏移地址 */
}
}
return NULL;
}
实际应用算法
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
/* 匹配算法比较次数记录 */
int str_countor = 0;
int kmp_countor = 0;
char* strstr_cpy(char src[],char des[])
{
int length_src = strlen(src);
int length_des = strlen(des);
int i = 0;
int j = 0;
if(length_des <= 0 || length_src <= 0)
{
return 0;
}
while(i<length_src)/* 遍历源字符串 */
{
while(j<length_des)/* 遍历目标字符串 */
{
str_countor++;
if(src[i+j] == des[j]) /* 如果当前位置的字符和目标字符相同,则持续往后移动 */
{
j++;
}
else /* 如果有任意一个字符不匹配,则重新开始 */
{
i++; /* 源字符串后移,查找字符串重新开始匹配 */
j = 0;
}
}
if(j >= length_des)
{
return src+i; /* 返回查找字符串在源字符串中第一次出现的偏移地址 */
}
}
return NULL;
}
/*
* * 函数功能:获取查找字符串的next值
* */
void kmp_get_next_index(char src[],int next[])
{
int m = strlen(src);
int i=0,j=0;
next[0] = 0; //字符串起始的next为0
for(i=1;i<m;i++)//遍历查找字符串
{
for(j=0;j<i;j++) //遍历第i个字符前的字串,以计算next值
{
if(!strncmp(src,src+i-j,j+1)) //判断字符串的相等个数:'P0 - Pj' = 'Pi-j - Pi'
{
next[i] = j+1; //除匹配情况外,next值都是1
}
}
}
}
/*
* * 函数功能:依据next值进行kmp字符匹配
* */
char* kmp_test(char src[],char des[],int next[])
{
int length_src = strlen(src);
int length_des = strlen(des);
int i = 0;
int j = 0;
int k=0;
if(length_des <= 0 || length_src <= 0)
{
return NULL;
}
while( i<length_src )/* 遍历源字符串 */
{
j=0;
while( j<length_des )/* 遍历目标字符串 */
{
if(src[i+j] == des[j]) /* 如果当前位置的字符和目标字符相同,则持续往后移动 */
{
j++;
}
else /* 如果有任意一个字符不匹配,则重新开始 */
{
if(0 == j)
i++;
else
i += j - next[j]; /* 源字符串依据next数组进行跳转 */
if(i<length_src)
break; /* 查找字符串从头开始重新匹配 */
else
return NULL;
}
kmp_countor++; /* 记录比较次数 */
}
if(j >= length_des && i < length_src) /* 如果已经比较完成,则返回查找字符串在源字符串的起始地址 */
{
return src+i; /* 返回查找字符串在源字符串中第一次出现的偏移地址 */
}
}
return NULL;
}
int main()
{
char src[]="ABDFABDABEABDFHABDFABDABEABDFBSGADHSHDHDHDHDHDHSSAJDAHGDGDABDFABDABEABAFHGDABDFABDABEABDFHABDFABDABEABDFHGDGDABDFABDABEABDFHGDABDFABDABEABDFHABDFABDABEABDFHGDGDABDFABDABEABEFHGDABDFABDABEABDFHABDFABDABEABDFHGDGDABDFABDABEABcFHGD";
char des[]="ABEABc";
char* kmp_res = NULL;
int next[strlen(des)],i=0;
struct timeval start,end,tmp;
printf("要查找的字符串:%s\n\r",des);
printf("src长度:%d des的长度为:%d\n\r\n\r",strlen(src),strlen(des));
memset(next,0,sizeof(next));
/* 计算运行时间 */
gettimeofday(&start, NULL); //获取程序开始时间
printf("[暴力匹配]:共比较%d次,目标字符串地址偏移量:%d!\n",str_countor,(int)(strstr_cpy(src,des)-src));
gettimeofday(&end, NULL);
printf("暴力匹配算法比较字符串耗时:%ldus\n\r\n\r",(end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec));
gettimeofday(&start, NULL); //获取程序开始时间
/* 获取特征值 */
kmp_get_next_index(des,next);
gettimeofday(&tmp, NULL);
kmp_res = kmp_test(src,des,next);
printf("[KMP算法]:共比较%d次,目标字符串地址偏移量:%d!\n",kmp_countor,(int)((NULL == kmp_res ? (src+strlen(src)):kmp_res)-src));
gettimeofday(&end, NULL);
printf("KMP匹配算法总耗时:%ldus,其中获取next耗时:%ldus,比较字符串耗时:%ldus\n\r",\
(end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec),\
(tmp.tv_sec - start.tv_sec) * 1000000 + (tmp.tv_usec - start.tv_usec),\
(end.tv_sec - tmp.tv_sec) * 1000000 + (end.tv_usec - tmp.tv_usec));
printf("[next index]:");
for(i=0;i<sizeof(next)/sizeof(int);i++)
printf("\n\r%c %2d",des[i],next[i]);
printf("\n\r");
return 0;
}
算法结果分析
针对以上字符串,暴力匹配需要查找354次,耗时:13us,
KMP需要查找113次,总共耗时:24us,其中next耗时:6us,比较查找耗时15us。
des[]="ABEABc" [next index]: 0 0 0 1 2 0
由于linux的调度会影响代码运行时间的计算,因此实际的运算以比较的次数为准,其中KMP的比较次数为计入扫描des字符串的时间。