目录
1 头文件
1.1 字符串匹配头文件
#include <iostream>
#include<iomanip>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include "Karp_Rabin.h"
using namespace std;
#define printString(s) { for (size_t m = strlen(s), k = 0; k < m; k++) printf("%4c", (s)[k]); }
void showProgress(char*, char*, int, int); //显示算法进程
void printNext(int*, int, int); //打印Next表
int* buildNext(char*,int); //构造Next表
void printBC(int*); //打印BC表
int* buildBC(char*,int); //构造BC表
void printGS(char*, int*); //打印GS表
int* buildGS(char*,int); //构造GS表
/******************************************************************************************
* 显示Next[]表,供演示分析
******************************************************************************************/
void printNext(int* N, int offset, int length) {
for (int i = 0; i < offset; i++) cout << " "; //printf(" ");
for (int i = 0; i < length; i++) cout << " "<<i; // printf("%4d", i);
cout << endl;// printf("\n");
for (int i = 0; i < offset; i++) cout << " ";
for (int i = 0; i < length; i++) cout << std::setw(4) << N[i]; //printf("%4d", N[i]);
cout << endl<<endl;// printf("\n\n");
}
/******************************************************************************************
* 显示bc[]表,供演示分析
******************************************************************************************/
void printBC(int* bc) {
cout << endl << "--------------- bc[] 表--------------- " << endl;
for (size_t j = 0; j < 256; j++)
if (0 <= bc[j])
cout << " " << (char)j; //printf("%4c", (char)j);
cout << endl;//printf("\n");
for (size_t j = 0; j < 256; j++) if (0 <= bc[j])
cout << " " << bc[j]; //printf("%4d", bc[j]);
cout << endl;//printf("\n\n");
}
/******************************************************************************************
* 显示GS[]表,供演示分析
******************************************************************************************/
void printGS(char* P, int* GS) {
cout << endl << "--------------- gs[] 表--------------- " << endl;
for (size_t m = strlen(P), j = 0; j < m; j++) cout << " " << j;// printf("%4d", j);
cout << endl;//printf("\n");
printString(P); cout << endl;// printf("\n");
for (size_t m = strlen(P), j = 0; j < m; j++)cout << " " << GS[j]; //printf("%4d", GS[j]);
cout << endl;//printf("\n\n");
}
/******************************************************************************************
* Text : 0 1 2 . . . i i+1 . . . i+j . . n-1
* ------------------------|-------------------|------------
* Pattern : 0 1 . . . j . .
* |-------------------|
******************************************************************************************
* 动态显示匹配进展
* i为P相对于T的起始位置,j为P的当前字符
******************************************************************************************/
void showProgress(char* T, char* P, int i, int j) {
static int step = 0; //操作计数器
cout << "========>执行到第" << ++step << "步!" << endl<<endl;//printf("\n-- Step %2d: --\n", ++step);
for (size_t n = strlen(T), t = 0; t < n; t++)
cout << std::setw(4) << t;//printf("%4d", t);
cout << endl;
printString(T);
cout << endl;
if (0 <= i + j)
{
for (int t = 0; t < i + j; t++) cout << " ";// printf("%4c", ' ');
cout << " |"; // printf("%4c", '|');
}
cout << endl; //printf("\n");
for (int t = 0; t < i; t++) cout << " ";//printf("%4c", ' ');
printString(P);
cout << endl; //printf("\n");
}
1.2 Karp_Rabin头文件
#include <cstring>
#define M 97 //散列表长度:既然这里并不需要真地存储散列表,不妨取更大的素数,以降低误判的可能
#define R 10 //基数:对于二进制串,取2;对于十进制串,取10;对于ASCII字符串,取128或256
#define DIGIT(S, i) ( (S)[i] - '0' ) //取十进制串S的第i位数字值(假定S合法)
using HashCode = __int64; //用64位整数实现散列码
#define showProgressOfKR(T, P, k) { \
showProgress(T, P, k, 0); \
printf("该串的哈希值 = %I64d", hashT); getchar(); \
}
bool check1by1(char* P, char* T, size_t i); //指纹相同时,逐位比对以确认是否真正匹配
HashCode prepareDm(size_t m);//预处理:计算R^(m - 1) % M (仅需调用一次,不必优化)
void updateHash(HashCode& hashT, char* T, size_t m, size_t k, HashCode Dm);// 子串指纹快速更新算法
//指纹相同时,逐位比对以确认是否真正匹配
bool check1by1(char* P, char* T, size_t i) {
for (size_t m = strlen(P), j = 0; j < m; j++, i++) //尽管需要O(m)时间
if (P[j] != T[i]) return false; //但只要散列得当,调用本例程并返回false的概率将极低
return true;
}
//预处理:计算R^(m - 1) % M (仅需调用一次,不必优化)
HashCode prepareDm(size_t m) {
HashCode Dm = 1;
for (size_t i = 1; i < m; i++) Dm = (R * Dm) % M; //直接累乘m - 1次,并取模
return Dm;
}
// 子串指纹快速更新算法
void updateHash(HashCode& hashT, char* T, size_t m, size_t k, HashCode Dm) {
hashT = (hashT - DIGIT(T, k - 1) * Dm) % M; //在前一指纹基础上,去除首位T[k - 1]
hashT = (hashT * R + DIGIT(T, k + m - 1)) % M; //添加末位T[k + m - 1]
if (0 > hashT) hashT += M; //确保散列码落在合法区间内
}
1.3 字符串暴力匹配头文件
#include <cstdlib>
#include <cstdio>
#include <cstring>
void showProgress(char*, char*, int, int); //显示算法进程
//蛮力匹配算法1
int Brute_matchA(char* P, char* T,int Plength, int Tlength) { //串匹配算法(Brute-force-1)
size_t n = Tlength, i = 0; //文本串长度、当前接受比对字符的位置
size_t m = Plength, j = 0; //模式串长度、当前接受比对字符的位置
while (j < m && i < n) //自左向右逐个比对字符
{
if (i + Plength > Tlength) break;
showProgress(T, P, i - j, j); getchar();
if (T[i] == P[j]) //若匹配
{
i++; j++;
} //则转到下一对字符
else //否则
{
i -= j - 1; j = 0;
} //文本串回退、模式串复位
}
return i - j; //如何通过返回值,判断匹配结果?
}
//蛮力匹配算法2
int Brute_matchB(char* P, char* T,int Plength, int Tlength) { //串匹配算法(Brute-force-2)
size_t n =Tlength, i = 0; //文本串长度、与模式串首字符的对齐位置
size_t m = Plength, j; //模式串长度、当前接受比对字符的位置
for (i = 0; i < n - m + 1; i++) { //文本串从第i个字符起,与
for (j = 0; j < m; j++) //模式串中对应的字符逐个比对
{
if (i + Plength > Tlength) break;
showProgress(T, P, i, j); getchar();
if (T[i + j] != P[j]) break; //若失配,模式串整体右移一个字符,再做一轮比对
}
if (j >= m) break; //找到匹配子串
}
return i; //如何通过返回值,判断匹配结果?
}
2.相关算法
2.1 kmp算法
//构造模式串P的next表
int* buildNextA(char* P,int Plength) {
size_t m = Plength, j = 0; //“主”串指针
int* N = new int[m]; //next表
int t = N[0] = -1; //模式串指针
while (j < m - 1)
if (0 > t || P[j] == P[t]) { //匹配
j++; t++;
N[j] = t; //此句可改进...
}
else //失配
t = N[t];
printString(P); cout << endl;//printf("\n");
printNext(N, 0, m);
return N;
}
//KMP算法
int matchKMP(char* P, char* T,int Plength, int Tlength) {
int* next = buildNext(P,Plength); //构造next表
int n = Tlength, i = 0; //文本串指针
int m = Plength, j = 0; //模式串指针
while (j < m && i < n) //自左向右逐个比对字符
{
if (i + Plength > Tlength) break;
showProgress(T, P, i - j, j);
printNext(next, i - j, strlen(P));
getchar();//暂停算法,观看步骤
cout << endl;//printf("\n");
if (0 > j || T[i] == P[j]) //若匹配,或P已移出最左侧(两个判断的次序不可交换)
{
i++; j++;
} //则转到下一字符
else //否则
j = next[j]; //模式串右移(注意:文本串不用回退)
}
delete[] next; //释放next表
return i - j;
}
//构造模式串P的next表(改进版本)
int* buildNext(char* P,int Plength) {
size_t m =Plength, j = 0; //“主”串指针
int* N = new int[m]; //next表
int t = N[0] = -1; //模式串指针
while (j < m - 1)
if (0 > t || P[j] == P[t]) { //匹配
N[j] = (P[++j] != P[++t] ? t : N[t]); //注意此句与未改进之前的区别
}
else //失配
t = N[t];
printString(P); cout << endl;// printf("\n");
printNext(N, 0, m);
return N;
}
2.2 Boyer-Moore算法
// Boyer-Moore算法
int matchBMBC(char* P, char* T,int Plength,int Tlength) { //Boyer-Morre算法(简化版,只考虑Bad Character Shift)
int* bc = buildBC(P,Plength); //预处理
size_t n = Tlength, i = 0; //文本串长度、与模式串首字符的对齐位置
size_t m = Plength; //模式串长度
while (n >= i + m) { //在到达最右端前,不断右移模式串(可能不止一个字符)
int j = m - 1; //从模式串最末尾的字符开始
while (P[j] == T[i + j]) //自右向左比对
if (0 > --j) break; showProgress(T, P, i, j); getchar();
if (j < 0) //若极大匹配后缀 == 整个模式串,则说明已经完全匹配,故
break; //返回匹配位置
else //否则,根据BC表
i += max(1, j - bc[T[i + j]]); //相应地移动模式串,使得T[i+j]与P[bc[T[i+j]]]对齐
}
delete[] bc; //销毁BC表
return i;
}
//*****************************************************************************************
// 0 bc['X'] m-1
// | | |
// ........................X***************************************
// .|<------------- 'X' free ------------>|
//*****************************************************************************************
//构造Bad Charactor Shift表:O(m + 256)
int* buildBC(char* P,int Plength) {
int* bc = new int[256]; //BC表,与字符表等长
for (size_t j = 0; j < 256; j++) bc[j] = -1; //初始化:首先假设所有字符均未在P中出现
for (size_t m = Plength, j = 0; j < m; j++) //自左向右扫描模式串P
bc[P[j]] = j; //将字符P[j]的BC项更新为j(单调递增)——画家算法
printBC(bc);
return bc;
}
//Boyer-Morre算法(完全版,兼顾Bad Character与Good Suffix)
int matchBMGS(char* P, char* T,int Plength, int Tlength) {
int* bc = buildBC(P,Plength); int* gs = buildGS(P, Plength); //构造BC表和GS表
size_t i = 0; //模式串相对于文本串的起始位置(初始时与文本串左对齐)
while (Tlength >= i + Plength) { //不断右移(距离可能不止一个字符)模式串
int j = Plength - 1; //从模式串最末尾的字符开始
while (P[j] == T[i + j]) //自右向左比对
if (0 > --j) break; showProgress(T, P, i, j); cout << endl;//printf("\n");
getchar();
if (0 > j) //若极大匹配后缀 == 整个模式串(说明已经完全匹配)
break; //返回匹配位置
else //否则,适当地移动模式串
i += max(gs[j], j - bc[T[i + j]]); //位移量根据BC表和GS表选择大者
}
delete[] gs; delete[] bc; //销毁GS表和BC表
return i;
}
//构造最大匹配后缀长度表:O(m)
int* buildSS(char* P,int Plength) {
int m = Plength; int* ss = new int[m]; //Suffix Size表
ss[m - 1] = m; //对最后一个字符而言,与之匹配的最长后缀就是整个P串
// 以下,从倒数第二个字符起自右向左扫描P,依次计算出ss[]其余各项
for (int lo = m - 1, hi = m - 1, j = lo - 1; j >= 0; j--)
if ((lo < j) && (ss[m - hi + j - 1] < j - lo)) //情况一
ss[j] = ss[m - hi + j - 1]; //直接利用此前已计算出的ss[]
else { //情况二
hi = j; lo = min(lo, hi);
while ((0 <= lo) && (P[lo] == P[m - hi + lo - 1])) //二重循环?
lo--; //逐个对比处于(lo, hi]前端的字符
ss[j] = hi - lo;
}
cout << endl<<"--------------- ss[] 表 ---------------" << endl;
for (int i = 0; i < m; i++) cout << " " << i;//printf("%4d", i);
cout << endl;// printf("\n");
printString(P); cout << endl; //printf("\n");
for (int i = 0; i < m; i++) cout <<" "<< ss[i];// printf("%4d", ss[i]);
cout << endl<<endl;// printf("\n\n");
return ss;
}
//构造好后缀位移量表:O(m)
int* buildGS(char* P,int Plength) {
int* ss = buildSS(P, Plength); //Suffix Size table
size_t m = strlen(P); int* gs = new int[m]; //Good Suffix shift table
for (size_t j = 0; j < m; j++) gs[j] = m; //初始化
for (size_t i = 0, j = m - 1; j < UINT_MAX; j--) //逆向逐一扫描各字符P[j]
if (j + 1 == ss[j]) //若P[0, j] = P[m - j - 1, m),则
while (i < m - j - 1) //对于P[m - j - 1]左侧的每个字符P[i]而言(二重循环?)
gs[i++] = m - j - 1; //m - j - 1都是gs[i]的一种选择
for (size_t j = 0; j < m - 1; j++) //画家算法:正向扫描P[]各字符,gs[j]不断递减,直至最小
gs[m - ss[j] - 1] = m - j - 1; //m - j - 1必是其gs[m - ss[j] - 1]值的一种选择
printGS(P, gs);
delete[] ss; return gs;
}
2.3 Karp_Rabin算法
// Karp_Rabin算法
int matchKR(char* P, char* T,int Plength,int Tlength) { //串匹配算法(Karp-Rabin)
size_t m = Plength, n = Tlength; //assert: m <= n
HashCode Dm = prepareDm(m), hashP = 0, hashT = 0;
for (size_t i = 0; i < m; i++) { //初始化
hashP = (hashP * R + DIGIT(P, i)) % M; //计算模式串对应的散列值
hashT = (hashT * R + DIGIT(T, i)) % M; //计算文本串(前m位)的初始散列值
}
cout << "该匹配串的散列(哈希)值为:" << hashP; // printf("hashP = %I64d", hashP);
getchar();
for (size_t k = 0; ; ) { //查找
showProgressOfKR(T, P, k);
if (hashT == hashP) {
cout << "散列(哈希)匹配成功!" << endl;//printf("Hash matched\n");
if (check1by1(P, T, k))
return k;
}
if (++k > n - m) return k; //assert: k > n - m,表示无匹配
else updateHash(hashT, T, m, k, Dm); //否则,更新子串散列码,继续查找
}
}
2.4 蛮力算法在头文件中
3. 完整代码
#include "StringMatch.h"
#include "StringBruteMatch.h"
int match(char* P, char* T,int i) {
static int poistion=0;//记录匹配位置
//事先计算出P与T的长度,方便后期调用,不用在重复计算
int Plength = strlen(P);//P的长度
int Tlength = strlen(T);//T的长度
if (Plength > Tlength) {
cout << "模式串的长度大于文本串的长度,不具有匹配性!";
return -1;
}
switch (i)
{
case 0: {//KMP匹配
cout << right<<setw(80)<<"++++++++++++++++ 调用KMP匹配 ++++++++++++++++" << endl;
poistion = matchKMP(P, T, Plength, Tlength);
break;
}
case 1: { //BM_BC匹配
cout << right << setw(80) << "++++++++++++++++ 调用BM_BC匹配 ++++++++++++++++" << endl;
poistion = matchBMBC(P, T,Plength,Tlength);
break;
}
case 2: {// BM_BCGS匹配
cout << right << setw(80) << "++++++++++++++++ 调用BM_BCGS匹配 ++++++++++++++++" << endl;
poistion = matchBMGS(P, T,Plength,Tlength);
break;
}
case 3: {//暴力匹配A
cout << right << setw(80) << "++++++++++++++++ 暴力匹配A ++++++++++++++++" << endl;
poistion = Brute_matchA(P, T,Plength,Tlength);
break;
}
case 4: {//暴力匹配B
cout << right << setw(80) << "++++++++++++++++ 暴力匹配B ++++++++++++++++" << endl;
poistion = Brute_matchA(P, T,Plength,Tlength);
break;
}
default: {// Karp_Rabin 匹配
cout << right << setw(80) << "++++++++++++++++ 调用Karp_Rabin匹配 ++++++++++++++++" << endl;
poistion = matchKR(P,T,Plength,Tlength);
break;
}
}
if (Plength < (poistion + Tlength) && (Plength + poistion) <Tlength + 1 )
cout << "匹配位置在:" << poistion;//printf("Matching found at #%d\a\n", KMPpos);
else
cout << "没有找到匹配位置,匹配失败!"; // printf("No matching found\a\a\n");
cout << endl;
return poistion;
}
int main() {
char s2[] = "abcgabcdabcefgabcesdcsefaf";//文本串
char s1[] = "abcdabc";//模式串
system("cls");
cout << " 文本:"; printString(s2);cout<<endl;
cout<<" 匹配串: "; printString(s1); cout<<endl;
srand((unsigned int)time(NULL));
int i = rand() % 6;
match(s1,s2,i);
//int KMPpos = matchKMP(s1, s2);
// if (strlen(s1) < (KMPpos + strlen(s2) )&& (strlen(s1) + KMPpos)< strlen(s2)+1)
// cout << "匹配位置在:" << KMPpos;//printf("Matching found at #%d\a\n", KMPpos);
// else
// cout << "没有找到匹配位置,匹配失败!"; // printf("No matching found\a\a\n");
// cout << endl;
// int BM_BCpos = matchBMBC(s1, s2);
// if (strlen(s1) < (BM_BCpos + strlen(s2)) && (strlen(s1) + BM_BCpos) < strlen(s2) + 1)
// cout << "匹配位置在:" << BM_BCpos;//printf("Matching found at #%d\a\n", KMPpos);
// else
// cout << "没有找到匹配位置,匹配失败!"; // printf("No matching found\a\a\n");
// cout << endl;
//int BM_BCGSpos = matchBMGS(s1, s2);
//if (strlen(s1) < (BM_BCGSpos + strlen(s2)) && (strlen(s1) + BM_BCGSpos) < strlen(s2) + 1)
// cout << "匹配位置在:" << BM_BCGSpos;//printf("Matching found at #%d\a\n", KMPpos);
//else
// cout << "没有找到匹配位置,匹配失败!"; // printf("No matching found\a\a\n");
//cout << endl;
//int KRpos = matchKR(s1, s2);
//if (strlen(s1) < (KRpos + strlen(s2)) && (strlen(s1) + KRpos) < strlen(s2) + 1)
// cout << "匹配位置在:" << KRpos;//printf("Matching found at #%d\a\n", KMPpos);
//else
// cout << "没有找到匹配位置,匹配失败!"; // printf("No matching found\a\a\n");
//cout << endl;
system("pause");
return 0;
}
4. 运行结果及截图
使用回车键运行
第一步
第二步
依次类推,如果不想逐步按键而想直接得到计算结果,可以去掉相关算法函数中的 getchar();
在visual studio 中直接使用ctrl键加H键得到
在点击全部替换即可!当然你也可以一步步查询去删除!