0.题目
在一个主串S={a, b, c, c, b, c, a, b, d}, 模式串T={a, b, d};请找出模式串在主串中第一次出现的位置
提示: 不需要考虑字符串大小写问题,字符均为小写字母
1.BF算法
Brute-Force算法,简称为 BF算法,是一种简单朴素的模式匹配算法,常用于在一个主串 S 内查找一个子串 T 的出现位置。
核心思想与操作是:
- 对于给定的主串 S 与子串 P ,主串 S 的长度为 N,子串 T 的长度为 M ;
- 首先,将 S[1] 和 T[1] 进行比较;
- 若相等,则再比较 S[2] 和 T[2] ,一直到 T[M] 为止;
- 若 S[1] 和 T[1] 不等,则 T 向右移动一个字符的位置,再依次进行比较
假设主串 S="abcababca";模式串T = "abcdex"
开始比较
字符串数组约定: 0位放字符串数组的长度
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0#define MAXSIZE 40 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
typedef char String[MAXSIZE+1]; /* 0号单元存放串的长度 *//* 生成一个其值等于chars的串T */
Status StrAssign(String T,char *chars)
{int i;if(strlen(chars)>MAXSIZE)return ERROR;else{T[0]=strlen(chars);for(i=1;i<=T[0];i++)T[i]=*(chars+i-1);return OK;}
}Status ClearString(String S)
{S[0]=0;/* 令串长为零 */return OK;
}/* 输出字符串T。 */
void StrPrint(String T)
{int i;for(i=1;i<=T[0];i++)printf("%c",T[i]);printf("n");
}/* 输出Next数组值。 */
void NextPrint(int next[],int length)
{int i;for(i=1;i<=length;i++)printf("%d",next[i]);printf("n");
}/* 返回串的元素个数 */
int StrLength(String S)
{return S[0];
}
思路:
- 分别利用计数指针i和j指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j的初值为1;
- 如果2个串均为比较到串尾,即i和j均小于等于S和T的长度时, 则循环执行以下的操作:
- S[i]和T[j]比较,若相等,则i 和 j分别指示串中下一个位置,继续比较后续的字符;
- 若不相等,指针后退重新开始匹配. 从主串的下一个字符串(i = i - j + 2)起再重新和模式第一个字符(j = 1)比较;
- 如果j > T.length, 说明模式T中的每个字符串依次和主串S找中的一个连续字符序列相等,则匹配成功,返回和模式T中第一个字符的字符在主串S中的序号(i-T.length);否则匹配失败,返回0;
int Index_BF(String S, String T,int pos){//i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配int i = pos;//j用于子串T中当前位置下标值int j = 1;//若i小于S的长度并且j小于T的长度时,循环继续while (i <= S[0] && j <= T[0]) {//比较的2个字母相等,则继续比较if (S[i] == T[j]) {i++;j++;}else{//不相等,则指针后退重新匹配//i 退回到上次匹配的首位的下一位;//加1,因为是子串的首位是1开始计算;//再加1的元素,从上次匹配的首位的下一位;i = i-j+2;//j 退回到子串T的首位j = 1;}}//如果j>T[0],则找到了匹配模式if (j > T[0]) {//i母串遍历的位置 - 模式字符串长度 = index 位置return i - T[0];}else{return -1;}
}
2.RK算法
1.思想:把需要对比的字符串转换成相应的哈希值
字母转换成哈希值,将当前的字母-'a'得到的数字
比如:
a - a = 0;
b - a = 1;
c - a = 2;
d - a = 3;
e - a = 4;
2.小写字母之间存在进制,即26进制
例如:
"cba" = 'c'*26*26 + 'b'*26 + 'a' *1= (c - a)*26^2 + (b - a)*26^1 + (a - a) *26^0= 2 * 26^2 + 1 * 26 + 0 * 1= 1352 + 26 + 0= 1378
代码:
//d 表示进制
#define d 26//4.为了杜绝哈希冲突. 当前发现模式串和子串的HashValue 是一样的时候.还是需要二次确认2个字符串是否相等.
int isMatch(char *S, int i, char *P, int m)
{int is, ip;for(is=i, ip=0; is != m && ip != m; is++, ip++)if(S[is] != P[ip])return 0;return 1;
}//3.算出最d进制下的最高位
//d^(m-1)位的值;
int getMaxValue(int m){int h = 1;for(int i = 0;i < m - 1;i++){h = (h*d);}return h;
}/** 字符串匹配的RK算法* Author:Rabin & Karp* 若成功匹配返回主串中的偏移,否则返回-1*/
int RK(char *S, char *P)
{//1. n:主串长度, m:子串长度int m = (int) strlen(P);int n = (int) strlen(S);printf("主串长度为:%d,子串长度为:%dn",n,m);//A.模式串的哈希值; St.主串分解子串的哈希值;unsigned int A = 0;unsigned int St = 0;//2.求得子串与主串中0~m字符串的哈希值[计算子串与主串0-m的哈希值]//循环[0,m)获取模式串A的HashValue以及主串第一个[0,m)的HashValue//此时主串:"abcaadddabceeffccdd" 它的[0,2)是ab//此时模式串:"cc"//cc = 2 * 26^1 + 2 *26 ^0 = 52+2 = 54;//ab = 0 * 26^1 + 1 *26^0 = 0+1 = 1;for(int i = 0; i != m; i++){//第一次 A = 0*26+2;//第二次 A = 2*26+2;A = (d*A + (P[i] - 'a'));//第一次 st = 0*26+0//第二次 st = 0*26+1St = (d*St + (S[i] - 'a'));}//3. 获取d^m-1值(因为经常要用d^m-1进制值)int hValue = getMaxValue(m);//4.遍历[0,n-m], 判断模式串HashValue A是否和其他子串的HashValue 一致.//不一致则继续求得下一个HashValue//如果一致则进行二次确认判断,2个字符串是否真正相等.反正哈希值冲突导致错误//注意细节://① 在进入循环时,就已经得到子串的哈希值以及主串的[0,m)的哈希值,可以直接进行第一轮比较;//② 哈希值相等后,再次用字符串进行比较.防止哈希值冲突;//③ 如果不相等,利用在循环之前已经计算好的st[0] 来计算后面的st[1];//④ 在对比过程,并不是一次性把所有的主串子串都求解好Hash值. 而是是借助s[i]来求解s[i+1] . 简单说就是一边比较哈希值,一边计算哈希值;for(int i = 0; i <= n-m; i++){if(A == St)if(isMatch(S,i,P,m))//加1原因,从1开始数return i+1;St = ((St - hValue*(S[i]-'a'))*d + (S[i+m]-'a'));}return -1;
}
使用并打印:
char *buf="abcababcabx";
char *ptrn="cabab";
printf("主串为%sn",buf);
printf("子串为%sn",ptrn);int index = RK(buf, ptrn);
printf("find index : %dn",index);