题目: 有一个主串S = {a, b, c, a, c, a, b, d, c}, 模式串T = { a, b, d } ; 请找到模式串在主串中第一次出现的位置;
提示: 不需要考虑字符串大小写问题, 字符均为小写字母;
1.BF算法
BF算法其实就是暴力解法
1.从头开始遍历主串 取出主串的第i个字符
2.如果S[i]!=T[0], 则继续取i+1 进行比较
3.如果S[i]==T[0], 则继续比较后面的字符S[i+j]和T[j]
4.如果直到j==strlen(T), 字符都相等 则说明匹配上了 返回位置i
5.如果直到i小于strlen(S)-strlen(T)+1,也就是S后续的字符长度比T的小了, 都没匹配上, 则返回-1;
int Index_BF(char *S, char *T,int pos){
if (strlen(T)==0) {
return -1;
}
for (int i=pos; i<strlen(S)-strlen(T)+1; i++) {
if (S[i]==T[0]) {
int flag = 1;
for (int j = 1; j<strlen(T); j++) {
if (S[i+j] != T[j]) {
flag = 0;
break;
}
}
if (flag == 1) {
return i;
}
}
}
return -1;
}
2.RK算法
如果两个字符串hash后的值不相同,则它们肯定不相同;如果它们hash后的值相同,它们不一定相同。
RK算法的基本思想就是:将模式串T的hash值跟主串S中的每一个长度为|T|的子串的hash值比较。如果不同,则它们肯定不相等;如果相同,为了避免冲突,再将子串与模式串的字符逐个比较,最终确认是否相同。
2.1 hash值的转换
数字的比较是单个值的比较,而字符的比较则需要逐个字符比较。如果我们能设计一个一个转换hash公式,将不同的字符组合,映射成不同的数字,这样字符串的比较就会简单很多。
我们知道 657 = 6 *10 * 10 + 5 * 10 + 7 * 1
数字657我们可以根据位数和进制进行拆解, 变成各个位数的累加值。进制为10。
那么我们也可以这样转换字符串abc: abc = a * 26 * 26 + b * 26 + c。进制为26。但是上面的a仍然是用字符表示的。
如何将a也转换成数字呢?用ascii码表示吗? 不可以。 因为如果使用ASC码时,计算数字过大。容易导致类型溢出。所以。我们要设计一个规则. 将字母-'a'的值作为该字母所对应的数字。
也就是:
a对应的值为a-a=0
b对应的值为b-a=1
c对应的值为c-a=2
...
这样一来,abc = 0 * 26 * 26 + 1 * 26 + 2
= 28
2.2 字符串的拆解求值
我们可以在比较之前, 做一次for循环,计算出主串的每个子串的hash值, 然后存入数组,在比较式取出。 这显然不是个好办法,如果中途就匹配到了,那后面的就白计算了。
我们还有另一种方法:
2.3 冲突解决
只要是hash,就有可能造成冲突问题。要想解决冲突可以设计更复杂的哈希公式。也可以再进行一次字符的挨个比较,进行最终确认;
3.代码实现
//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,子串长度为:%d\n",n,m);
//A.模式串的哈希值; St.主串分解子串的哈希值;
unsigned int A = 0;
unsigned int St = 0;
//2.求得子串与主串中0~m字符串的哈希值[计算子串与主串0-m的哈希值]
//循环[0,m)获取模式串A的HashValue以及主串第一个[0,m)的HashValue
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+1
St = (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个字符串是否真正相等.反正哈希值冲突导致错误
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;
}