C++ 数据结构学习 ---- 串

目录

1 头文件

1.1 字符串匹配头文件

1.2 Karp_Rabin头文件

1.3 字符串暴力匹配头文件

2.相关算法

2.1 kmp算法

2.2 Boyer-Moore算法

2.3 Karp_Rabin算法

2.4 蛮力算法在头文件中

3. 完整代码

4. 运行结果及截图


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键得到

 在点击全部替换即可!当然你也可以一步步查询去删除!

猜你喜欢

转载自blog.csdn.net/qq_58240448/article/details/128115977