KMP(字符串匹配)+字符串哈希

KMP非常不好理解,建议在网上搜KMP的视频看看,反正非常难理解,我想了好久好久(KMP的关键就在于求Next数组D,求前缀后缀)
下面的D题,B题稍稍有变化 ,C题只用求前缀后缀就只用求next数组

标准求next数组做法,
  int len=moshi.length();
	  n[0]=-1;
	  int k=-1;
	  int j=0;
	  while(j<len-1){
	      if(k==-1||moshi[j]==moshi[k]){
	      ++k;
	      ++j;
	      n[j]=k;
	    }else{
	      k=n[k];
	    }
	}

问题 A: 统计不同字符串的个数

题目描述
如题,给出n个字符串,让你输出其中共有多少个不同的字符串。请使用字符串哈希解决本题。
输入
第一行一个正整数n表示有n个字符串。
接下来n行每行一个字符串,每个字符串可以由数字和大小写字母组成,大小写敏感。
1 ≤ n ≤ 1000,每个字符串最大长度为1500
输出
输出一行包含一个整数,表示其中不同字符串的个数。
样例输入
5
abc
aaaa
abc
abcc
12345
样例输出
4

本题用哈希的方法做:一个字符串对应一个哈希值,然后把这个哈希值对应到一个数组的坐标上。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+5;
#define p 13
#define mod 101
set<string> ss;
int a[maxn];

int hashh(string s){
    int len=s.length();
    int sum=0;  //哈希值
    for(int i=0;i<len;i++){
      sum+=(s[i]*p)%mod;
    }
    if(a[sum]==0){   //对应到数组左标上  为0说明以前没加过,然后赋值为一,返回值为1
       a[sum]=1;
       return 1;
     }
    else
    return 0     //否则说明这个字符串以前加过;返回零
}
int main(){
    string s;
    int n;
    while(cin>>n){
      memset(a,0,sizeof(a));
      for(int i=0;i<n;i++){
        if(hashh(s))   //为1时说明以前没加过,然后加到set里(用vector也行)
            ss.insert(s);
      }
      cout<<ss.size()<<endl;  //然后直接用set的大小值输出
    }
}

//下面这个为标准做法
#include <cstdio>
#include <iostream>
#include <map>
#include <set>
#include <cstring>
using namespace std;
const int maxn = 1507;
const int p = 13;
const int mod = 101;
int Hash[maxn];
int main()
{
    int n;
    cin >> n;
    set<int> ss;
    while(n--){
        string tmp;
        cin >> tmp;
        int i;
        for(i = 0; i < tmp.size(); i++){
            if(i == 0) Hash[i] = int(tmp[i] - 'a');
            else Hash[i] = (Hash[i-1] * p + int(tmp[i] - 'a' + 1)) % mod;
        }
        ss.insert(Hash[i-1]);
        memset(Hash, 0, sizeof Hash);
    }
    cout << ss.size() << endl;
    return 0;
}

问题 B: 赵神的兼职

题目描述
赵神最近在做一份兼职——帮人查一串字符串中某个单词的出现次数。
当工作太多时,赵神就忙不过来啦,所以他想找一名助手帮他。
输入
第一行输入一个 T ,代表数据数目。
每组数据第一行输入需要查找的单词。
第二行输入这个较长的字符串(长度小于1000010)。
输出
每组数据输出一个 n ,代表要这个单词出现的次数。
样例输入
3
ABCD
ABCD
AZA
AZAZAZA
HIDSJ
FJOSJWHABNMDS
样例输出
1
3
0

kmp算法做,但是也不完全和kmp一样,因为第二种情况按kmp是应该输出2的,但是他是3,是因为他是统计这个出现次数
相当于j走完了i并没有j加一位 ,而是直接在这一位上继续比较,
                              那这个情况就变成next数组多求一位
AZA                           AZAx                x是空格,他肯定是不匹配的,他会移动到第二个位置
  j                              j                    AZAX
AZAZAZA                       AZAZAZA               AZAZAZA   
  i                             i 
#include<bits/stdc++.h>
using namespace std;
string moshi,mubiao;
const int maxn=1000010;
int n[maxn];

void init(){     //求next数组   根据next[j]=k(j位置发生失配时,回到位置k)求next[j+1]等于多少
     memset(n,0,sizeof(n));
  int len=moshi.length();
  n[0]=-1;     //next数组的第一个赋值为-1
  int k=-1;   //第一次k赋值为1
  int j=0;  //下标从0开始
  while(j<len){   //kmp本来是小于len-1,多求一位就变成小于len
    if(k==-1||moshi[j]==moshi[k]){   //k=-1说明此时回到的是头部 moshi[j]=moshi[k]说明有后缀和前缀
      ++k;                           //此时当前k+1;
      ++j;                            //j的位置走加1
      n[j]=k;                          //将K的值赋值给n[j];
    }else{  //否则k的值变为回到位置上的值
      k=n[k];
    }
  }
}

int kmp(){
   int lenmoshi=moshi.length(),lenmubiao=mubiao.length();
   int i=0,j=0,sum=0;
   while(i<lenmubiao){    //走目标串
          if(mubiao[i]==moshi[j]){     //如果目标串的对应元素和模式串的对应元素相等    
                i++;
                j++;
          }else if(j>=0){//回到位置是值是大于等于0的
                j=n[j];  //失配时,模式串的位置变到n数组位置上的值     失配时,模式串移到哪   失配时,先走模式串先走回到哪,在进行匹配
          }else{  //代表j=-1了说明第一个就发生了失配  目标串的位置+1 
             ++i;
             ++j;//j+1变为0  下一次进入循环就变为mubiao[i]是否等于moshi[0]
             }
          if(j==lenmoshi){   //走完了模式串
                sum++;
                }
   }
   return sum;
}
int main(){
        int n;
        cin>>n;
        while(n--){
          cin>>moshi>>mubiao;
           init();
           cout<<kmp()<<endl;
        }
}

问题 C: 测姻缘

题目描述
小林最近开始研究算命。
虽然他不会算命,但是他可以自己创造一个算命的算法
当一个人的名字的前缀,与另一个人的名字后缀,相同的字母越多,这两个人就越有可能成为情侣。(此处前后缀可以包括名字本身)。
这个算姻缘的方法使得许多妹子前来测试。然而,由于他最近忙着写线段树,没空帮人算姻缘啦。
这里还有许多没测完呢。你能帮帮美丽的妹子们算算吗?
输入
输入有多组数据。
每组数据有两行,分别为两个人的名字(名字全由英文组成,名字长度不超过50000)。
输出
求出前一个人的名字的前缀,与后一个人的名字的后缀,最大的相同数目。
若不为0,还需输出其相同的几位字母,并且字母在数字前面,中间由空格隔开。
样例输入
mike
aniom
kiava
dvakia
dasds
fdsgh
样例输出
m 1
kia 3
0

#include<bits/stdc++.h>
using namespace std;
const int maxn=50000+5;
int Next[maxn];
string s1,s2;

void inint_next(string s){
  memset(Next,0,sizeof(Next));

}

int main(){
    string s1,s2;
    while(cin>>s1>>s2){
       string s=s1+s2;
      // cout<<s<<endl;
      int len=s.length();
      int len1=s1.length();
      int j=0,k=-1;
      Next[0]=-1;
      while(j<len){
        if((k==-1||s[k]==s[j])&&k<len1){
          ++k;
          ++j;
          Next[j]=k;
        }else{
          k=Next[k];
        }
      }
       //for(int i=0;i<len;i++){
         //cout<<Next[i]<<" ";
    //   }
    //   cout<<endl;
       int cnt=Next[len];
       for(int i=0;i<cnt;i++){
         cout<<s[i];
       }
       if(cnt!=0) cout<<" ";
       cout<<cnt<<endl;
    }
}

问题 D: 左左的牛肉串

题目描述
左左有N串长度比较短的牛肉串,俊俊有一串长度比较长的牛肉串。牛肉串都是由字符组成,俊俊想知道左左的每一串牛肉串在自己的牛肉串中出现的次数。
输入
输入多组数据。
输入一个数字N代表左左的N串牛肉串。1 <= N <= 10。
接下来N行为左左的牛肉串中的字符。字符串长度不超过10。
第N+1行,俊俊的牛肉串中的字符。字符串长度不超过100。
输出
输出一个数字代表左左的N串牛肉串在俊俊的牛肉串中出现的次数。
样例输入
5
she
he
say
shr
her
yasherhs
样例输出
3


kmp算法
#include<bits/stdc++.h>
using namespace std;
string mubiao;
const int maxn=1000010;
int n[maxn];
vector<string> v;
void initnext(string moshi){
     memset(n,0,sizeof(n));
  int len=moshi.length();
  n[0]=-1;
  int k=-1;
  int j=0;
  while(j<len-1){
    if(k==-1||moshi[j]==moshi[k]){
      ++k;
      ++j;
      n[j]=k;
    }else{
      k=n[k];
    }
  }
}

int kmp(string moshi){
   int lenmoshi=moshi.length(),lenmubiao=mubiao.length();
   int i=0,j=0,sum=0;
   while(i<lenmubiao-1){   
          if(j==-1||mubiao[i]==moshi[j]){     
                i++;
                j++;
          }else{
                j=n[j];  
          }
          if(j==lenmoshi)
                sum++;
   }
   return sum;
}
int main(){
        int n;
        cin>>n;
        int ans=0;
        string moshi;
        while(n--){
          cin>>moshi;
           v.push_back(moshi);
        }
        cin>>mubiao;
        vector<string> ::iterator it;
        for(it=v.begin();it!=v.end();it++){
            initnext(*it);
            ans+=kmp(*it);
        }
        cout<<ans<<endl;
}

问题 E: 红红的小兔子

题目描述
红红的棚子里养着一群兔子。
有一天,红红想要研究兔子的 DNA 序列和兔子的长相。
首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。
然后每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。
请使用字符串hash

输入
第一行输入一个 DNA 字符串 S。
第二行一个数字 m,表示 m 次询问。
1 ≤length(S)m≤1000000
接下来 m 行,每行四个数字 L1 R1 L2 R2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。
输出
对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。
样例输入
aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2
样例输出
Yes
No
Yes

//哈希做法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1000015;
const int p = 1e5+3;       
//一般如果数据比较多比较大的话,p可以取一个六位数的素数,mod取1e9+7这样子
const int mod = 1e9+7;
char ch[maxn];
LL h[maxn], pi[maxn];

LL get(int l, int r)   //求一段区间的哈希值
{
    return ((h[r] - h[l - 1] * pi[r - l + 1]) % mod + mod) % mod;
}

int main()
{
    scanf(" %s", ch + 1);
    memset(h, 0, sizeof h);
    int n = strlen(ch + 1); //从ch[1]开始存
    pi[0] = 1;
    for (int i = 1; i <= n; i++) {
        h[i] = (h[i - 1] * p + ch[i] - 'a' + 1) % mod; //利用前缀和的思想求哈希值
        pi[i] = (pi[i - 1] * p) % mod;
    }
    int m;
    cin >> m;
    while (m--) {
        int l, r, x, y;
        cin >> l >> r >> x >> y;
        if (get(l, r) == get(x, y))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0;
}

//利用截取字符串方法做
#include<bits/stdc++.h>
using namespace std;

int main(){
  string s;
  cin>>s;
  int n;
  cin>>n;
  while(n--){
    int abegin,aend,bbegin,bend;
    cin>>abegin>>aend>>bbegin>>bend;
    string s1,s2;
    s1=s.substr(abegin-1,aend-abegin+1);
    s2=s.substr(bbegin-1,bend-bbegin+1);
  //  for(int i=abegin-1;i<aend;i++)
  //    s1+=s[i];
  //  for(int j=bbegin-1;j<bend;j++)
  //      s2+=s[j];
  if(s1==s2)
           cout<<"Yes"<<endl;
         else
           cout<<"No"<<endl;
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字符串匹配算法是一种用来查找一个字符串(即目标串)在另一个字符串(即模式串)中的出现位置的算法。其中,KMP算法是一种比较常用的字符串匹配算法。 KMP算法的核心思想是通过利用模式串中已经匹配过的信息,来尽量减少目标串和模式串的比较次数,从而提高匹配效率。它利用一个最长公共前缀和最长公共后缀数组,记录模式串中已经匹配成功的前缀和后缀的长度。通过根据这些信息来移动模式串的位置,避免不必要的比较。 而字符串算法是一种将字符串映射为一个较短的固定长度的数值的算法。通过对字符串的每个字符进行一系列运算,如求幂、取模等,最终得到一个哈值。这个哈值可以代表该字符串的特征,不同字符串的哈值一般不会相同。 字符串算法的主要作用是将字符串转化为一个定长的数字,方便在数据结构中进行比较和存储。在字符串匹配中,使用哈算法可以将目标串和模式串转换为哈值,然后比较哈值是否相等来判断是否匹配。由于比较哈值的时间复杂度较低,使用字符串算法可以提高匹配效率。 总的来说,字符串匹配算法和字符串算法都是用来处理字符串匹配的问题。KMP算法通过利用已知信息来减少比较次数,提高匹配效率;而字符串算法则是将字符串转化为哈值,便于进行比较和存储。两者都在一定程度上提高了字符串匹配的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值