现在的位置: 首页 > 综合 > 正文

【字符串系列】柔性字符串匹配代码实现

2018年04月12日 ⁄ 综合 ⁄ 共 5093字 ⁄ 字号 评论关闭
http://www.cnblogs.com/jackiesteed/articles/2865457.html

柔性字符串匹配, 介绍各种字符串匹配算法, 用来学习字符串算法不错.

下面是我自己用C++实现的算法代码, 陆续贴上来...

ShiftAnd算法:

复制代码
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>


using namespace std;

typedef unsigned long long ULL;

//ShiftAnd算法
//做一些假定: 传入的模式串和text串都是小写字母.
//为了减少一些复杂度.
// 整形最长64位, 所以模式串最长支持64
// 返回-1说明没有找到, 否则返回匹配的开始位置.
int ShiftAnd(char pat[], char text[])
{
    ULL B[26];
    int len_pat = strlen(pat);
    if(len_pat > 64)
    {
        cout << "模式串长度超过64, 运算不支持..." << endl;
        return -2;
    }

    //这里是初始化B数组的过程
    memset(B, 0, sizeof(B));
    for(int i = 0; i < 26; i++)
    {
        for(int j = 0; j < len_pat; j++)
        {
            if(pat[j] == i + 'a')
            {
             B[i] |= ULL(1LL << j);
            }
        }
    }

    ULL D = 0;

    int len_text = strlen(text);
    for(int i = 0; i < len_text; i++)
    {
        //这个式子是关键, 
        //掩码D的第j + 1位是活跃的, 当且仅当D的j是活跃的(p[1...j]是t[1...i]的后缀), 而且p[j + 1] == t[i + 1]
        // !!!!!记住一个意义, 就是D的第x是1, 说明pat的前x位是t[1...i]的后缀..
        D = ((D << 1) | 1LL) & B[text[i] - 'a'];
        if((D & ULL(1LL << (len_pat - 1))) != 0)
        {
            return i - len_pat + 1;
        }
    }
    return -1;
}
复制代码

ShiftOr算法:

复制代码
#include <iostream>
#include <fstream>
#include <algorithm>
#include <climits>
#include <cstdlib>




using namespace std;
typedef unsigned long long ULL;

//ShiftOr算法
//做一些假定: 传入的模式串和text串都是小写字母.
//为了减少一些复杂度.
// 整形最长64位, 所以模式串最长支持64
// 返回-1说明没有找到, 否则返回匹配的开始位置.
void BinaryDump(ULL x)
{
    for(int i = 0; i < 64; i++)
    {
        if(x & ULL(1LL << (63 - i))) cout << 1;
        else cout << 0;
    }
    cout << endl;
}

int ShiftOr(char pat[], char text[])
{
    int len_pat = strlen(pat);
    if(len_pat > 64)
    {
        cout << "模式串长度超过64, 运算不支持..." << endl;
        return -2;
    }
    ULL B[26];
    //memset(B, 0, sizeof(B));
    for(int i = 0; i < 26; i++)
        B[i] = 0;
    //cout << B[0] << endl;
    for(int i = 0; i < 26; i++)
    {
        for(int j = 0; j < len_pat; j++)
        {
            if(pat[j] == i + 'a')
            {
                B[i] |= ULL(1LL << j);
            }
        }
    }
    
    //这里获得B的反码.
    for(int i = 0; i < 26; i++)
    {
        B[i] ^= (-1LL);
    }

    ULL D = ULL(-1LL);
    int len_text = strlen(text);
    for(int i = 0; i < len_text; i++)
    {
        D = (D << 1) | B[text[i] - 'a'];
        if((D & ULL(1LL << (len_pat - 1))) == 0)
            return i - len_pat + 1;
    }
    return -1;
}
复制代码

 Horspool算法:

复制代码
int Horspool(char pat[], char text[])
{
    int len_pat = strlen(pat);
    int len_text = strlen(text);
    if(len_pat > len_text) return -1;

    int d[256];
    for(int i = 'a'; i <= 'z'; i++)
    {
        d[i] = len_pat;
    }
    for(int i = 0; i < len_pat - 1; i++)
    {
        d[pat[i]] = len_pat - i - 1;
    }
    int pos = 0;
    while(pos <= len_text - len_pat)
    {
        int i = len_pat - 1;
        while(i >= 0 && text[pos + i] == pat[i]) i--;
        if(i == -1)
            return pos;
        pos += d[text[pos + len_pat - 1]];
    }

    return -1;
}
复制代码

 后缀数组

参考链接: http://blog.csdn.net/jokes000/article/details/7839686

复制代码
#include <iostream>
#include <fstream>
#include <cstring>


using namespace std;

const int MAXN = 100000;
int rank[MAXN];
int sa[MAXN];
char str[MAXN];

bool change_abc(int r[], char str[])
{
    int idx = 0;
    for(; str[idx] != '\0'; idx ++)
    {
        if(str[idx] >= 'A' && str[idx] <= 'Z') r[idx] = str[idx] - 64;
        else if(str[idx] >= 'a' && str[idx] <= 'z') r[idx] = str[idx] - 70;
        else return false;

    }

    return true;
}

bool cmp(int r[], int i, int j, int l)
{
    return r[i] == r[j] && r[i + l] == r[j + l];
}

//m是字典里面字符的个数
//n是长度
//sa是最终的suffixarray这个数组
void suffix_array(int rank[], int sa[], int n, int key_num)
{
    int count[MAXN], i, secondkey[MAXN], len, *_secondkey = secondkey, *_rank = rank, *tmp, p;

    //count用于计数排序
    //len表示倍增法里面每次的子串长度, 不断*2
    for(int i = 0; i <= key_num; i++)count[i] = 0;
    for(int i = 0; i < n; i++) count[_rank[i]]++;

    for(int i = 1; i <= key_num; i++) count[i] += count[i - 1];
    for(int i = n - 1; i >= 0; i--)sa[--count[_rank[i]]] = i;

    //到这里, sa[i] 表示该位置是目前可以确定的第i大后缀的位置.
    //默认算在后面的更大一些
    //rank表示, 第i位置的后缀应该排第几, 跟sa是对应的.

    //_secondkey表示对第二关键字的排序
    for(p = 1, len = 1; p < n; len *= 2, key_num = p)
    {
        //下面这两行是对于第二关键字的排序
        //为什么要把n-j到n-1这几个放到最前面呢? 因为他们后面没有数据, 当做空来算, 最小
        for(p = 0, i = n - 1; i >= n - len; i--, p++) _secondkey[p] = i;
        for(i = 0; i < n; i++) if(sa[i] >= len) _secondkey[p++] = sa[i] - len;


        //count是一个中间数组, 里面的内容的意思大致是, 对key的计数, 然后用这个计数来排序, 可以理解为是使用了计数排序
        //下面这4行就是典型的基数排序了, 可以看出来跟函数最开始的初始化差不多
        for(i = 0; i <= key_num; i++) count[i] = 0;
        for(i = 0; i < n; i++) count[_rank[_secondkey[i]]] ++;
        //_rank[_secondkey[i]], 基数排序
        for(i = 1; i <= key_num; i++) count[i] += count[i - 1];
        for(i = n - 1; i >= 0; i--) sa[--count[_rank[_secondkey[i]]]] = _secondkey[i];

        //_rank表示第一个关键字,
        //也表示算法里面描述的那个rank数组

        //下面需要比较, 是因为有些相邻的是相同的串
        for(tmp = _rank, _rank = _secondkey, _secondkey = tmp, _rank[sa[0]] = 0, i = 1, p = 1; i < n; i++)
        {
            _rank[sa[i]] = cmp(_secondkey, sa[i], sa[i - 1], len) ? p - 1: p++;
        }

    }
}


int main()
{
//    freopen("input.txt", "r", stdin);

    cin >> str;
    int len = strlen(str) + 1;

    change_abc(rank, str);

    suffix_array(rank, sa, len, 52);

    for(int i = 1; i < len; i++)
    {
        cout << sa[i] << " ";
    }
    cout << endl;

    for(int j = 1; j < len; j++)
        printf("%s\n", str + sa[j]);



    return 0;
}
复制代码

 BNDM算法, 子串匹配

复制代码
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <climits>
#include <cstdio>



using namespace std;
typedef unsigned long long ULL;
const int MAXN = 300;

//BNDM算法, 全名Backward Nondeterministic Dawg Matching, 来自柔性字符串匹配, 是一个子串匹配算法, 利用并行运算.
//做一些假定: 传入的模式串和text串都是小写字母.
//为了减少一些复杂度.
// 整形最长64位, 所以模式串最长支持64
// 返回-1说明没有找到, 否则返回匹配的开始位置.
//-2说明模式串超过了64长度, 暂不支持.
//为了简化, 只支持小写字母..
int BNDM(char pat[], char text[])
{
    int len_pat = strlen(pat);
    if(len_pat > 64)
    {
        cout << "模式串长度超过64, 运算不支持..." << endl;
        return -2;
    }
    int len_text = strlen(text);
    ULL B[MAXN] = {0};


    for(int i = 0; i < len_pat; i ++)
    {
        B[int(pat[i])] |= ULL(1LL << (len_pat - i - 1)); //为什么用len_pat -i -1? 因为是要利用pat的反转的串
    }


    int pos = 0;
    while(pos <= len_text - len_pat)
    {
        int idx = len_pat - 1;
        int last = len_pat - 1;
        ULL mask = ULL(-1LL); //初始化成1111111,表示模式串的每一个位置都能与空串相匹配..
        while(mask != 0LL)
        {
            mask = mask & B[int(text[pos + idx])];
            idx -= 1;
            if((mask & ULL(1LL << (len_pat - 1))) != 0LL) //如果进行与运算结果不是0,
                                                          // 那么说明目前取到的这一个子串是模式串的前缀, len_pat - idx是前缀长度,
                                                          //如果idx ==0, 那么就完全匹配到了..
            {
                if(idx >= 0) last = idx; //last用来移动窗口
                else return idx + pos + 1;//如果idx==0, 那说明已经完全匹配了.
            }
            mask <<= 1;
            //mask的某一位是1, 就说明, 从这1位开始的len_pat-1-idx的长度的串, 是同当前从text里读入的串相同..
        }
        pos += last + 1;
    }
    return -1;
}



int main()
{
    char pat[] = "a";
    char text[] = "aaaaaaabcddd";

    cout << BNDM(pat, text) << endl;
    return 0;
}
复制代码

 

抱歉!评论已关闭.