题目:有一个主串S = {a, b, c, a, c, a, b, d, c}, 模式串T = { a, b, d } ; 请找到模式串在主串中第一次出现的位置;
提示: 不需要考虑字符串大小写问题, 字符均为小写字母
要找到模式串第一次在主串中出现的次数,我们可以提供BF与RK两种算法,现在我们先看BF算法
BF算法
- 定义:即暴力(Brute Force)算法,是普通的模式匹配算法
- 算法思想:将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
- 算法逻辑图示:
typedef char String[100+1]; /* 0号单元存放串的长度 *//**
思路:主串S与模式串T的首字符位置是字符串的长度,所以遍历字符串从1开始
1、利用计数指针i与j分别指向主串S与模式串T中正在比较的字符,i从1开始,j从1开始,startIndex用来记录i开始的位置
2、当S[i] == T[j] 时i++,j++,继续遍历
3、当 S[i] != T[j] 时,计数指针回退到主串的下一个字符开始,i = i - j + 2(因为从1开始计算,再加1就是下一个开始的字符),j回退到 j = 1;
4、当j > strlen(T)时,表示匹配完成,匹配到了,此时startIndex就是开始的位置
*/
int BFAlgorithm(String S, String T){
if (strlen(T)>strlen(S)) {
return -1;
}
int startIndex = 1;
int i = 1,j = 1,sLength = (int)S[0],tLength = (int)T[0];
while (i <= sLength && j <= tLength) {
if (S[i] != T[j]) {
i = i - j + 2;
startIndex = i;
j = 1;
}else{
j++;
i++;
}
}
if (j <= tLength) {//没有找到
startIndex = -1;
}
return startIndex;
}
int StrAssign(String T,char *chars)
{
int i;
if(strlen(chars)>100)
return -1;
else
{
T[0]=strlen(chars);
for(i=1;i<=T[0];i++)
T[i]=*(chars+i-1);
return 0;
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("算法匹配之BF算法\n");
String S,T;
StrAssign(S, "jkdjkfhds");
StrAssign(T, "kfh");
int index = BFAlgorithm(S, T);
if (index == -1) {
printf("主串是:%s,\n模式串是:%s\n没有找到\n",S,T);
}else{
printf("主串是:%s,\n模式串是:%s\n找到了位置:%d\n",S,T,index);
}
return 0;
}
- 时间复杂度:O(n*m),其中n是主串的长度,m是模式串的长度
RK算法
- 算法思想:利用两个字符串的哈希值来比较字符串是否相同
- 算法逻辑图:
- 哈希算法:又叫散列,比如大家常见的MD5加密算法就是哈希,主要应用于文件校验、数字签名以及鉴权协议。
- 如何将字符串转成哈希值:
- 将 ‘当前字母’ - ‘a’ = 数字,比如: a - a = 0,b - a = 1,c - a = 2等
- 字符串每个字母转成数字后再组合,比如:
'cab’ = 'c' * 26 * 26 + 'a' * 26 + 'b' * 1
= 2 * 26^2 + 0 * 26 + 1 * 1
= 1353
此时字符串'cab'就转成1353数值了(26是26个字母,这个值越大,计算出来的不同字符串 哈希值相等的概率越小)
- 主串拆解子串哈希值计算图解
- 子串哈希值计算规律:相邻的2个子串 s[i] 与 s[i+1] (i表示子串从主串中的起始位置,子串的长度都为m). 对应的哈希值计算公式有交集. 也就说我们可以使用s[i-1]计算出s[i]的哈希值;
- 代码实现
//d用质数,值越大,哈希值冲突的可能性越小 static int d = 26; //获得d的(m - 1)次方的值 long getMaxValue(int m){ long maxValue = 1; for (int i = 1; i<=m - 1; i++) { maxValue = maxValue * d; } return maxValue; } //比对字符串是否相同 int isMatch(char *S, char *T, int start, int lenght){ for (int i = 0; i<lenght; i++) { if (S[start + i] != T[i]) { return 0; } } return 1; } /** 思路: 1、先计算出模式串以及主串中模式串长度的子串哈希值 2、计算出d进制下的最高位的d^(m - 1)数值,m为模式串的长度 3、拆解主串并计算出子串的哈希值 4、比对子串的哈希值跟模式串的哈希值大小 ① 如果两个哈希值不同,则继续步骤3、4 ② 如果两个哈希值相同,为了解决哈希冲突,再比较两个字符串是否相同,如果相同则找到了,不同则继续步骤3、4 */ int RKAlgorithm(char *S, char *T){ int n = (int)strlen(S); int m = (int)strlen(T); long long sHashValue = 0,tHashValue = 0; /** 以S = "cbdfaiurnd",T = "dfa"为例, 第一次循环: sHashValue = 0 * 26 + 2 = 2; tHashValue = 0 * 26 + 3 = 3; 第二次循环: sHashValue = 2 * 26 + 1 = 53; tHashValue = 3 * 26 + 5 = 83; 第三次循环: sHashValue = 53 * 26 + 3 = 1381; tHashValue = 83 * 26 + 0 = 2158; */ for (int i = 0; i<m; i++) { sHashValue = sHashValue * d + (S[i] - 'a'); tHashValue = tHashValue * d + (T[i] - 'a'); } //计算出子串最高位d^(m - 1)的值 long maxValue = getMaxValue(m); for (int i = 0; i<=n-m; i++) { if (tHashValue == sHashValue) { if (isMatch(S, T, i, m) == 1) { //从1开始计算 return i + 1; } }else{ //公式:St[i] =(st[i-1] - d^2 ✖ (s[i] - ‘a’)) ✖ d + (s[i+m]-‘a’) sHashValue = (sHashValue - maxValue * (S[i] - 'a')) * d + (S[i + m] - 'a'); printf("子串哈希值为:%lld\n",sHashValue); } } return -1; } int main(int argc, const char * argv[]) { // insert code here... printf("字符串匹配之RK算法\n"); char *S = "cbdfaiurnd"; char *T = "dfa"; printf("主串为:%s,\n模式串为:%s\n",S,T); printf("匹配位置:%d (注意:-1为没有匹配到)\n",RKAlgorithm(S, T)); return 0;} - 时间复杂度:如果不考虑哈希值冲突的判断字符串是否相同,则时间复杂度O(n),否则是O(n*m)
- 哈希值冲突解决 : 由于计算字符串哈希值,总会有一定的几率导致不同字符串的哈希值是相同的,这个时候就产生了哈希值冲突。解决方法有两种:
第一种:进制数d尽可能的使用质数比较大的值,这样可以减少哈希值出现冲突的概率
第二种:当哈希值相同的时候,比对子串跟模式串是否一样(本文使用的就是这个)