[字符串算法]字符串算法及其例题集锦

KMP算法,字符串算法,在CSDN博客这里,有很多人写的很好,非常地详细,让人自叹不如。然而我很懒,不想看太多文字,只想通俗地理解这KMP,所以写下这篇文章,文章很简陋,只是基本搞懂KMP的原来,没有去深究它,如果这文章对大家没有帮助,请忽略。

1、字符串hash进阶

字符串hash是指将一个字符串s映射为一个整数,使得该整数可以尽可能唯一地代表字符串s。那么在一定程度上,如果两个字符串转换成地整数相等,就可以认为这两个字符串相同。
[字符串算法]字符串算法及其例题集锦_第1张图片
直达链接:原题地址
[字符串算法]字符串算法及其例题集锦_第2张图片

#include 
#include
#include
#include
using namespace std;
const int Mod = 1e9+7;
const int p = 1e7+19;
vector<int>arr;
long long hashfunc(string str){
   long long Hash = 0;
   for(int i=0;i<str.size();i++){
     Hash = (Hash*p + str[i] - 'a')%Mod;
   }
   return Hash;
}
int main(){
   string str;
   int n;
   cin>>n;
   for(int i=0;i<n;i++)
   {
       cin>>str;
       long long Hash=hashfunc(str);
       arr.push_back(Hash);
   }
   sort(arr.begin(),arr.end());///字符串排序
   int ans = 0;
   for(int i=0;i<arr.size();i++)
      if(i==0||arr[i]!=arr[i-1]){
        ans++;
      }
      cout<<ans<<endl;
      return 0;
}

2、KMP算法

2.1 暴力匹配算法

在了解KMP算法之前我们先了解一下一个暴力匹配算法,对于字符串的匹配问题我们很多人都可能一开始想到的是暴力,没错!!!如果题目数据量不大,可以选择这个算法:
[字符串算法]字符串算法及其例题集锦_第3张图片
暴力匹配核心代码:

int stringMatch(char *text,char *pattern){
    int tlen = strlen(text);
    int plen = strlen(pattern);
    int i=0;
    int j=0;
    while(i<tlen&&j<tlen){
        if(text[i]==pattern[j]){
            i++;
            j++;
        }
        else{
            i=i-j+1;
            j=0;
        }
        
        if(j==plen) return i-j;///匹配成功,返回模式串在主串中的位置
        else return -1;///匹配失败
    }
}

暴力匹配算法的复杂度为O(nm),所以如果数据量比较大的话,这种算法就无法接受了。

2.2 KMP算法

b站上讲得比较通俗易懂的两个视频:

1.字符串匹配算法1
2.字符串匹配算法2

Knuth-Morris-Pratt字符串查找算法,简称为"KMP算法",这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人共同发现的。这算法用于判断字符串pattern是否为text的字串。
在学KMP算法之前,我们先来学习一个next数组,这个数组对于实现KMP算法非常重要,等同于求出next数组就差不多可以实现字符串匹配了。如:子串s[0…i],其长度为k+1的前缀和后缀分别为s[0…k]和s[i-k…i]。nexi[i]就是使子串s[0…i]的前缀s[0…k]等于后缀s[i-k…i]的最大的k(最大公共前后缀的长度)。

[字符串算法]字符串算法及其例题集锦_第4张图片
注意:next[i]就是最大公共前后缀中地前缀的最后一位的小标,且最大公共前后缀长度必须小于本身

虽然上面图让我们明白求next数组的基本原来,但是我们在程序中怎么去编代码一一地找出最大公共前后缀地长度呢?
第①步:j=0,i=1→s[1] ! = s[0],回溯到 j=0→next[1]=j=0。

[字符串算法]字符串算法及其例题集锦_第5张图片

第②步:当前j=0,i=2→回溯s[2]!=s[1],s[2]==s[0],跳出循环,j=0→next[2]=j+1=1。

[字符串算法]字符串算法及其例题集锦_第6张图片

第③步:当前j=1,i=3→s[3]!=s[1],s[3]!=s[0],j=0→next[3]=j=0。
[字符串算法]字符串算法及其例题集锦_第7张图片

第④步:当前j=0→s[4]==s[0],j=0跳出循环→next[4]=j+1=1。

[字符串算法]字符串算法及其例题集锦_第8张图片

第⑤步:当前j=1→s[5]==s[1],j=1跳出循环→next[5]=j+1=1+1=2。

[字符串算法]字符串算法及其例题集锦_第9张图片

第⑥步:当前j=2→s[6]==s[2],j=2跳出循环→next[6] = j+1 = 2+1=3。

[字符串算法]字符串算法及其例题集锦_第10张图片

第⑦步:当前j=3→s[7]!=s[3],s[7]==s[0],j=0,跳出循环→next[7]=j+1=1。

[字符串算法]字符串算法及其例题集锦_第11张图片

获取数组next的值:

void getNext(char s[]){
    int len = strlen(s);
   int j = 0;
   next[0] = 0;
   for(int i = 1;i<len;i++) {///由于第一个字符肯定next为0(因为最大公共前后缀不能为本身),所以从1开始
    while(s[i]!=s[j]&&j!=0) j = next[j];///j=0跳出循环

    if(s[i]==s[j]) j++;
    next[i] = j;
   }
}

kmp算法实现:

bool kmp(char *text,char *pattern){
  int n = strlen(text);
  int m = strlen(pattern);
  getNext(pattern);
  int j=0;
  for(int i=0;i<n;i++){
    while(j!=0&&text[i]!=pattern[j]) j = next[j];
    if(text[i]==pattern[j]) j++;
    if(j==m-1) return true;
  }
  return false;
}

3、KMP算法的扩展

3.1 BM算法

KMP的匹配是从模式串的开头开始匹配的,而1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法:Boyer-Moore算法,简称BM算法。该算法从模式串的尾部开始匹配,且拥有在最坏情况下O(N)的时间复杂度。比KMP算法的实际效能高。
BM算法定义了两个规则:
☞ 坏字符规则:
第①种情况: 模式串中不存在S字符:S与N不匹配,S为坏字符。

[字符串算法]字符串算法及其例题集锦_第12张图片
所有模式串字符移动模式串长度位:
在这里插入图片描述

第②种情况: 模式串与文本串都存在A字符,A与N不匹配,A为坏字符。

[字符串算法]字符串算法及其例题集锦_第13张图片
移动到模式串中从右到左的第一个A(最右边的公共字符)。
[字符串算法]字符串算法及其例题集锦_第14张图片

☞ 好后缀规则:

[字符串算法]字符串算法及其例题集锦_第15张图片

Y与A不匹配,需要移动:

[字符串算法]字符串算法及其例题集锦_第16张图片

移动方法一:按照坏字符规则模式串移动了3步。
[字符串算法]字符串算法及其例题集锦_第17张图片

移动方法二:按照“好后缀原则”,模式串移动了6步。模式串有公共前后缀,直接移动到前缀与当前文本串公共字符对应位置
[字符串算法]字符串算法及其例题集锦_第18张图片

3.2 Sunday算法

Subday算法比BM算法效能更快,Sunday算法由Daniel M.Sunday在1990年提出,它的思想跟BM算法很相似:Sunday算法是从前往后匹配,在匹配失败时关注的是文本串参加匹配的最末位字符的下一个字符。如果该字符没有在匹配中出现则直接跳过,即移动步长=模式串长度+1;否则,同BM算法一样移动步长模式串中最右端的该字符到末尾的距离+1。

4、参考资料

1.从头到尾彻底理解KMP(2014年8月22日版)
2.《算法笔记》
3.http://blog.chinaunix.net/uid-22237530-id-1781825.html

你可能感兴趣的:(苦瓜僧学算法)