字典树vskmp

5 篇文章 0 订阅
2 篇文章 0 订阅

本篇博客旨在巩固基础代码学习,不足之处还望广大大牛批评指出不胜感激

一字典树

字典树目的在于可以节省大量空间存储单词或其他字符串,并且可以快速查找。

poj3630

题意:

给出若干字符串,判断某个字符串是否是其他字符串的前缀,有这样的字符串输出“NO”,否则输出“YES”。

思路:

根据字符串输的顺序不同需要考虑第一种:该字符串是否是上面输入过的字符串的前缀

                                                   第二种:该字符串是否包含之前输入的某个字符串

注意:

根据题目给定的范围可以算出字典树节点数最多有10*10000=100000,节点数少的话会wrongRE;

输入字符串之前要getchar(),以防输入n后的回车被读入,否则会RE;

C++代码如下(数组实现字典树插入,此题还未涉及字典树查找)

#include<stdio.h>
#include<math.h>
#include<string>
#include<algorithm>
#include<string.h>
using namespace std;
int tot;
int s[100010][10];
int iwn[100010];
int insertt(char *a,int rt)
{
    int l=strlen(a);
    for(int i=0; i<l; i++)
    {
        int u=a[i]-'0';
        if(s[rt][u])
        {
            rt=s[rt][u];
            if(i==l-1&&iwn[rt]==0)return 0;
        }
        else
        {
            s[rt][u]=tot++;
            rt=s[rt][u];
            iwn[rt]=0;
        }
        if(iwn[rt]==1)return 0;
    }
    iwn[rt]=1;
    return 1;
}
int main()
{
    int t;
    scanf("%d",&t);
    char a[12];
    while(t--)
    {
        int n;
        memset(s,0,sizeof(s));
        memset(iwn,-1,sizeof(iwn));
        tot=0;
        scanf("%d",&n);
        getchar();
        int rt=tot++;
        int h=0;
        for(int i=0; i<n; i++)
        {
            gets(a);
            if(insertt(a,rt)==0)
            {
                h=1;
            }
        }
        if(h==0)printf("YES\n");
        else printf("NO\n");
    }
return 0;
}
二 KMP算法
KMP算法是为了解决字符串快速匹配需求的算法,其中next数组起了举足轻重的作用,具体实现建议参看小甲鱼的一个视频非常详尽,这里涉及到了一个字符串的前缀和后缀的概念(本人也是历时n个小时才参透了其中的一二,甚是惭愧,写下这篇博客纪念一下)
c++模板:
#include<stdio.h>
#include<string>
#include<iostream>
#include<string.h>
using namespace std;
string s,t;
int next[100000];
int tlen=0;
int slen=0;
void getnext()
{
    int i,j;
    i=-1;
    j=0;
    next[0]=-1;
    while(j<tlen)
    {
        if(t[i]==t[j]||i==-1)
        {
            i++;
            j++;
            next[j]=i;
        }
        else
        {
            i=next[i];
        }
    }
}
int indexfind_()//主串中第一次出现模式串的位置
{
    int i=0,j=0;

    while(j<tlen &&i<slen)//这个位置很奇妙,若是写成 while(j<t.size()&&i<slen)或者while(j<t.size()&&i<s.size())则会出错这个循环只会进行一遍了,不知道为什么
    {
        if(-1==j||s[i]==t[j])
        {
            i++;
            j++;

        }
        else
        {
            j=next[j];

        }
    }
    if(j==tlen)
        return (i-tlen);
    else
        return -1;
}
int count_kmp()
{
    int i=0,j=0;
    int ans=0;
    for(int i=0; i<slen; i++)
    {
        while(j!=-1&&s[i]!=t[j])
            j=next[j];
        j++;
        if(j==tlen)
        {
            ans++;
            j=next[j];
        }
    }
    return ans;
}

int main()
{

    while(cin>>s>>t)
    {
        memset(next,0,sizeof(next));
        getnext();
        tlen=t.size();
        slen=s.size();
        int u=indexfind_();
        if(u==-1)printf("NO:没有匹配\n");
        else
            printf("YES:第一次匹配的位置为:%d\n",u);
        printf("匹配的次数: %d\n",count_kmp());
    }
}
一道例题体会next数组神奇的应用
poj2752

题目大意:
Example: Father='ala', Mother='la', we have S = 'ala'+'la' = 'alala'. Potential prefix-suffix strings of S are {'a', 'ala', 'alala'}. Given the string S, could you help the little cat to write a program to calculate the length of possible prefix-suffix strings of S? (He might thank you by giving your baby a name:) 
给定字符串T,求出T的所有可能相同前后缀的长度。如: "alala"的前缀分别为{"a", "al", "ala", "alal", "alala"}, 后缀分别为{"a", "la", "ala", "lala", "alala"}. 其中有{"a", "ala", "alala"}是相同的,即输出1 3 5.

下图很清晰的表达出了思路,(借鉴了一个大牛的神图)
    

如上图,黑色线来代表字符串t,其长度是l,红色线的长度代表next[l],由next数组定义得next[l]前的字符串前缀和next[l]前的字符串的后缀及其所有子串完全相同(也就是两条线所对应的位置)。我们再求出next[l]位置处的next值,也就是图中蓝线对应的长度。同样可以得到两个蓝线对应的子串肯定完全相同,又由于第二段蓝线属于左侧红线的后缀,所以又能得到它肯定也是整个字符串的后缀。因此这道题,就是求出 l 处的next值,并一步一步的向下(递归)求出所有的next值直到next值等于零,然后倒序输出得到答案。

c++代码如下:

#include<stdio.h>
#include<string>
#include<iostream>
#include<string.h>
using namespace std;
string t;
int next[500000];
int tlen;
void getnext()
{
    int i,j;
    i=-1;
    j=0;
    next[0]=-1;
    while(j<t.size())
    {
        if(t[i]==t[j]||i==-1)
        {
            i++;
            j++;
            next[j]=i;

        }
        else
        {
            i=next[i];
        }
    }
}
int main()
{

    while(cin>>t)
    {
        memset(next,0,sizeof(next));
        getnext();
        tlen=t.size();
        int k=0,ans[500000]={0};
        int j=t.size();
        while(j!=0)
        {
           ans[k++]=next[j];
           j=next[j];
        }
       for(int i=k-2;i>=0;i--)
        printf("%d ",ans[i]);
       printf("%d\n",tlen);
    }
}


 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值