柔性字符串匹配, 介绍各种字符串匹配算法, 用来学习字符串算法不错.
下面是我自己用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; }