zoukankan      html  css  js  c++  java
  • AC 自动机 数学建模

    今天看了一个数学建模题,随便就记录一下所查找的资料。下面都是来自网上的资料

    索引是一种特殊的数据结构在数据结构上实现高级查找算法,这种数据结构,就是索引。  原博客  http://blog.jobbole.com/24006/

    创建索引并不会改变表中的数据,它只是创建了一个新的数据结构指向数据表;打个比方,平时我们使用字典查字时,首先我们要知道查询单词起始字母,然后翻到目录页,接着查找单词具体在哪一页,这时我们目录就是索引表,而目录项就是索引了    原博客  http://www.educity.cn/wenda/404360.html

    感觉很明显 这个题还是应该用AC自动机,当然,这只是个人意见 仅供参考了

    字符串开到9亿都没问题(已经亲自测试过) char str[900000000];

    AC自动机算法  http://blog.csdn.net/niushuai666/article/details/7002823

    AC自动机 (这个博客很好,也很好理解,重点我已经标记了)

    原博客 http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d
     

    关键字:AC自动机 自动机 有限状态自动机 Trie 字母树 字符串匹配 多串匹配算法

    Note:阅读本文需要有KMP算法基础,如果你不知道什么是KMP,请看这里:

    http://www.matrix67.com/blog/article.asp?id=146   (Matrix67大牛写的)

    AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文章,让你在文章中找这些串是否出现过,在哪出现。也许你考虑过AC自动机名字的含义,我也有过同样的想法。你现在已经知道KMP了,他之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。那么AC自动机也是同样的,他是Aho-Corasick。所以不要再YY地认为AC自动机是AC(cept)自动机,虽然他确实能帮你AC一点题目。

    。。。扯远了。。。

    要学会AC自动机,我们必须知道什么是Trie,即字母树。如果你会了,请跳过这一段

            Trie是由字母组成的。

           先看张图: 

    这就是一棵Trie树。用绿色标出的点表示一个单词的末尾(为什么这样表示?看下去就知道了)。树上一条从root到绿色节点的路径上的字母,组成了一个“单词”。

           /* 也许你看了这一段,就知道如何构建Trie了,那请跳过以下几段。*/

            那么如何来构建一棵Trie呢?就让我从一棵空树开始,一步步来构建他。

    一开始,我们有一个root:

     

    现在,插入第一个单词,she。这就相当于在树中插入一条链。过程很简单。插完以后,我们在最后一个字母’e’上加一个绿色标记,结果如图:

            再来一个单词,shr(什么词?…..右位移啊)。由于root下已经有’s’了,我们就不重复插入了,同理,由于’s’下有’h’了,我们也略过他,直接在’h’下插入’r’,并把’r’标为绿色。结果如图:

           按同样的方法,我们继续把余下的元素插进树中。

           最后结果:

        

          

    好了,现在我们已经有一棵Trie了,但这还不够,我们还要在Trie上引入一个很强大的东西:失败指针或者说shift数组或者说Next函数 …..你爱怎么叫怎么叫吧,反正就是KMP的精华所在,这也是我为什么叫你看KMP的原因。

    KMP中我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符,当A[i+1]<>B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。

    Trie树上的失败指针与此类似。

            假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:设root到j的距离为n,则从k之上的第n个节点到k所组成的长度为n的单词,与从root到j所组成的单词相同。

            比如图中she中的’e’的失败指针就应该指向her中的’e’。因为:

         

    图中红框部分是完全一样的。

    那么我们要怎样构建这个东西呢?其实我们可以用一个简单的BFS搞定这一切。

    对于每个节点,我们可以这样处理:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字目也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root

    最开始,我们把root加入队列(root的失败指针显然指向自己),这以后我们每处理一个点,就把它的所有儿子加入队列,直到搞完。

    至于为什么这样就搞的定,我们讲下去就知道了。

    好了,现在我们有了一棵带失败指针的Trie了,而我的文章也破千字了,接下来,我们就要讲AC自动机是怎么工作的了。

    AC自动机是个多串匹配,也就是说会有很多串让你查找,我们先把这些串弄成一棵Trie(也就是把那些k-mer串弄成一个树,相当于建立索引,输入一个K,相当于k-mer串已经确定了,就可以建个tree树了),再搞一下失败指针,然后我们就可以开始AC自动机了。

    一开始,Trie中有一个指针t1指向root,待匹配串(也就是“文章”,也就是100个1000000串连起来,可能匹配的时候会把2个串当成一个串匹配,我也想到解决办法了,就是在每两个相邻串之间加一个符号,只要不是AGCT中的一个就行,这样就不会把两个串当成一个串匹配了)中有一个指针t2指向串头。

    接下来的操作和KMP很相似:如果t2指向的字母,是Trie树中,t1指向的节点的儿子,那么t2+1,t1改为那个儿子的编号,否则t1顺这当前节点的失败指针向上找,直到t2是t1的一个儿子,或者t1指向根。如果t1路过了一个绿色的点,那么以这个点结尾的单词就算出现过了(位置也很好判断,因为每个串的长度都是固定的,都是100,这样就能判断k-mer串出现的次数和出现在DNA中的序列了)。或者如果t1所在的点可以顺着失败指针走到一个绿色点,那么以那个绿点结尾的单词就算出现过了。

    我们现在回过来讲讲失败指针。实际上找失败指针的过程,是一个自我匹配的过程。

    如图,现在假定我们确定了深度小于2(root深度为1)的所有点的失败指针,现在要确定e。这就相当于我们有了这样一颗Trie:

    而文章为’she’,要查找’e’在哪里出现。我们接着匹配’say’,那’y’的失败指针就确定了。

    好好想想。前面讲的BFS其实就是自我匹配的过程,这也是和KMP很相似的。

    如何判断k-mer串之前有没出现过呢,可以用map<string,int> 来判断

    DNA序列和编号对映的时候或许还会用到map<int,int>;

    AC自动机建立和搜索的复杂度及证明 :  http://blog.csdn.net/happytengfei/article/details/8036362

    接下来就是上AC自动机代码了

      1 #include <iostream>
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include <algorithm>
      5 #include <queue>
      6 using namespace std;
      7 
      8 char str[1010][100];
      9 struct Trie
     10 {
     11     int next[1010*50][128],fail[1010*50],end[1010*50];
     12     int root,L;
     13     int newnode()
     14     {
     15         for(int i = 0;i < 128;i++)
     16             next[L][i] = -1;
     17         end[L++] = -1;
     18         return L-1;
     19     }
     20     void init()
     21     {
     22         L = 0;
     23         root = newnode();
     24     }
     25     void insert(char s[],int id)
     26     {
     27         int len = strlen(s);
     28         int now = root;
     29         for(int i = 0;i < len;i++)
     30         {
     31             if(next[now][s[i]] == -1)
     32                 next[now][s[i]] = newnode();
     33             now = next[now][s[i]];
     34         }
     35         end[now] = id;
     36     }
     37     void build()
     38     {
     39         queue<int>Q;
     40         fail[root] = root;
     41         for(int i = 0;i < 128;i++)
     42             if(next[root][i] == -1)
     43                 next[root][i] = root;
     44             else
     45             {
     46                 fail[next[root][i]] = root;
     47                 Q.push(next[root][i]);
     48             }
     49         while(!Q.empty())
     50         {
     51             int now = Q.front();
     52             Q.pop();
     53             for(int i = 0;i < 128;i++)
     54                 if(next[now][i] == -1)
     55                     next[now][i]=next[fail[now]][i];
     56                 else
     57                 {
     58                     fail[next[now][i]]=next[fail[now]][i];
     59                     Q.push(next[now][i]);
     60                 }
     61         }
     62     }
     63     int num[1010];//记录个数
     64     vector<int>post[1010];//记录位置
     65 
     66     void query(char buf[],int n)
     67     {
     68         for(int i = 0;i < n;i++)
     69             num[i] = 0,post[i].clear();
     70         int len=strlen(buf);
     71         int now=root;
     72         for(int i=0;i<len;i++)
     73         {
     74             now=next[now][buf[i]];
     75             int temp = now;
     76             while( temp != root )
     77             {
     78                 if(end[temp] != -1)
     79                 {
     80                     num[end[temp]]++;
     81                     post[end[temp]].push_back(i);
     82                 }
     83 
     84                 temp = fail[temp];
     85             }
     86         }
     87         for(int i = 0;i < n;i++)
     88         {
     89             if(num[i] > 0)
     90             {
     91                 printf("%s出现的个数是: %d
    ",str[i],num[i]);
     92                 printf("%s出现的结尾位置分别是
    ",str[i]);
     93                 for(int j=0;j<post[i].size();j++)
     94                     printf("%d ",post[i][j]);
     95                 printf("
    ");
     96             }
     97 
     98         }
     99 
    100     }
    101 
    102 };
    103 
    104 char buf[2000010];
    105 Trie ac;
    106 void debug()
    107 {
    108     for (int i = 0; i < ac.L; i++)
    109     {
    110         printf("id = %3d ,fail = %3d ,end = %3d, chi = [",i,ac.fail[i],ac.end[i]);
    111         for (int j = 0; j < 128; j++)
    112             printf("%2d ",ac.next[i][j]);
    113         printf("]
    ");
    114     }
    115 }
    116 int main()
    117 {
    118 //    freopen("in.txt","r",stdin);
    119 //    freopen("out.txt","w",stdout);
    120     int n;
    121     printf("请输入k-mer串的个数
    ");
    122     scanf("%d",&n);
    123     {
    124         ac.init();
    125         printf("请输入%d个k-mer串
    ",n);
    126         for(int i = 0;i < n;i++)
    127         {
    128             scanf("%s",str[i]);
    129             ac.insert(str[i],i);
    130         }
    131         ac.build();
    132         printf("请输入文本
    ");
    133         scanf("%s",buf);
    134         ac.query(buf,n);
    135     }
    136     return 0;
    137 }

    下面是我简单的测试了一下

    剩下就是一些细节问题了

  • 相关阅读:
    Linux命令汇总(二)
    关于pyspark
    关于CDH
    hive通过spark导入hbase
    CentOS7的网络配置
    TTY,Console以及Terminal
    docker的操作
    docker安装与操作
    Wmware Player中Linux挂载U盘
    Mesos和Marathon
  • 原文地址:https://www.cnblogs.com/tsw123/p/4449199.html
Copyright © 2011-2022 走看看