zoukankan      html  css  js  c++  java
  • 后缀数组

    后缀数组是根据一个给定的字符串,然后取这个字符串的所有后缀,然后将后缀排序,生成两个数组,sa数组和rank数组

    sa[i]存的是排名第i的字符串下标

    rank[i]存的是以下标i开头的后缀的排名 

    所以sa[rank[i]] = i    rank[sa[i]] = i

    由于字符串的比较是多关键字比较,如果用sort排序的话,时间复杂度是O(n*n*logn),这太不实用了

    如何高效的求出sa数组呢,可以用倍增算法来求,时间复杂度是O(n*logn)

    安徽省芜湖市第一中学 许智磊 《后缀数组》

    算法合集之《后缀数组——处理字符串的有力工具》

    这个论文写得很好了

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <stdlib.h>
      4 #include <algorithm>
      5 #include <iostream>
      6 #include <queue>
      7 #include <stack>
      8 #include <vector>
      9 #include <map>
     10 #include <set>
     11 #include <string>
     12 
     13 #pragma warning(disable:4996)
     14 typedef long long LL;                   
     15 const int INF = 1<<30;
     16 /*
     17 后缀是指从某个位置开始到字符串某位的  字符串
     18 suffix(i)表示 str[i-->len(str)-1];
     19 sa[i]表示所有的后缀中字典序第i大的字符串下标
     20 rank[i] 存的是 suffix(i) 在所有后缀中的排名
     21 
     22 
     23 */
     24 const int N = 100000*2 + 10;
     25 int rank[N], height[N], count[N], bucket[N], tmp[N];
     26 void buildSa(int *r, int *sa, int n, int m)
     27 {
     28     int *x=rank, *y = tmp, *t, p;
     29     for (int i = 0; i < m; ++i) count[i] = 0;
     30     for (int i = 0; i < n; ++i) count[x[i] = r[i]]++; 
     31     //这里的x是长度为字符串长度为1的rank数组,, 因为长度为1,所以排名就是该字符的大小
     32     for (int i = 1; i < m; ++i) count[i] += count[i - 1];
     33     for (int i = n - 1; i >= 0; --i) sa[--count[x[i]]] = i;
     34 
     35     for (int k = 1; k <= n; k <<= 1)
     36     {
     37         p = 0;
     38 
     39         //存的是第二关键字的sa数组
     40         //这些的第二关键字都是$,所以排名排在前面
     41         for (int i = n - k; i < n; ++i) y[p++] = i;
     42         //当前的字符串长度为k,所以下标大于k,才是第二关键字      y[] 存的是按第二关键字排序的字符串下标, sa[]本身就是有序的, 所以可以直接遍历
     43         for (int i = 0; i < n; ++i) if (sa[i] >= k)y[p++] = sa[i] - k;
     44 
     45         //y[i] 排名第i的字符串下标 (按第二关键字进行排序的)
     46         // x[y[i]]  是排名第i的字符串的第一关键字的大小    bucket收集第一关键字的大小
     47         for (int i = 0; i < n; ++i) bucket[i] = x[y[i]];
     48 
     49         //对第一关键字进行计数排序
     50         for (int i = 0; i < m; ++i) count[i] = 0;
     51         for (int i = 0; i < n; ++i) count[bucket[i]]++;
     52         for (int i = 1; i < m; ++i) count[i] += count[i - 1];
     53         //算出y[i] 的第一关键字大小 是排在多少
     54         for (int i = n - 1; i >= 0; --i) sa[--count[bucket[i]]] = y[i];
     55         
     56 
     57         //根据sa 算出rank      ,, 如果两个排名相近的两个字符串,  第一二关键字都相同, 则排名相同
     58         t = x; x = y; y = t;
     59         p = 1; x[sa[0]] = 0;
     60         for (int i = 1; i < n; ++i)
     61             x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
     62         if (p >= n) break;
     63         m = p;
     64     }
     65 }
     66 void makeHeight(int *r, int *sa, int n)
     67 {
     68     int i, j, k = 0;
     69     for (i = 1; i <= n; ++i) rank[sa[i]] = i;
     70     for (i = 0; i < n; height[rank[i++]]=k)
     71     for (k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++);
     72 }
     73 int r[N], sa[N];
     74 char str[N];
     75 int main()
     76 {
     77     int n1, n2, n3;
     78 
     79     while (scanf("%s", str) != EOF)
     80     {
     81         n1 = strlen(str);
     82         for (int i = 0; i < n1; ++i)
     83             r[i] = str[i] - 'a' + 2;
     84         r[n1++] = 1;
     85         n3 = n1;
     86         scanf("%s", str);
     87         n2 = strlen(str);
     88         for (int i = 0; i < n2; ++i)
     89             r[n3++] = str[i] - 'a' + 2;
     90         r[n3++] = 0;
     91         buildSa(r, sa, n3, 28);
     92         for (int i = 0; i < n3; ++i)
     93             printf("%d ", sa[i]);
     94         puts("");
     95         makeHeight(r, sa, n3 - 1);
     96         int ans = -1;
     97         for (int i = 1; i < n3; ++i)
     98         {
     99             
    100             if (height[i]>ans)
    101             {
    102                 if (sa[i] < n1 && sa[i - 1] >= n1)
    103                     ans = height[i];
    104                 else if (sa[i - 1] < n1 && sa[i] >= n1)
    105                     ans = height[i];
    106             }
    107         }
    108         printf("%d
    ", ans);
    109     }
    110     return 0;
    111 }
    View Code

    根据sa数组和rank数组,我们可以求出height数组

    height[i]表示排名第i的字符串和排名第i-1的字符串的最长前缀(LCP), 如果我们按顺序求 height[0] , height[1],height[2]...height[n] 那么时间复杂度是O(n*n)

    这是因为这样子算时,字符串的起始下标可能是不相邻的,所以没有充分利用信息

    我们可以定义一个辅助数组h[i] = height[rank[i]], 那么h[i] >= h[i-1] - 1

    证明: 设 suffix(k) 是排在suffix(i-1)前一名的字符串,则他们的最长公共前缀是h[i-1], 那么suffix(k+1) 肯定排在字符串 suffix(i)的前面, 且它们的最长公共前缀至少为h[i-1]-1

    因为suffix(k+1) 和 suffix(i) 相当于字符串suffix(k) 和suffix(i-1) 将第一个字符给去掉, 那它们的最长公共前缀不就是至少为h[i-1]-1

    又suffix(k+1) 肯定排在字符串 suffix(i)的前面, 但是可能是前几名,  如果 suffix(k+1) < suffix(j) < suffix(i) 呢 , 那suffix(j)  于 suffix(i)的最长公共前缀也至少为h[i-1]-1

    这是因为  如果suffix(k+1) 于suffix(i)前m个字符相等, 那么 suffix(k+1)与suffix(j)的前m个字符也相等,suffix(j)与suffix(i)的前m个字符也相等,但是第m+1个字符一定大于等于suffix(k+1)的第

    m+1个字符,且小于等于suffix(i)的第m+1个字符串,  这样子suffix(j)才会排在suffix(k+1)和suffix(i)中间

    所以只要按顺序求h[0] , h[1], h[2] .....就可以了

  • 相关阅读:
    详解SQL Server的两个存储过程:sp_MSforeachtable/sp_MSforeachdb
    使用WorkService创建定时任务
    Mahout下个性化推荐引擎Taste介绍
    Apache Mahout中的机器学习算法集
    内网信息收集笔记 楼下的小可怜
    关于linux的suid提权 楼下的小可怜
    Cobalt Strike初探 楼下的小可怜
    Google hacking 楼下的小可怜
    Git和Repo扫盲——如何取得Android源代码 zt
    Howto find native code memory leak in Android
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4604444.html
Copyright © 2011-2022 走看看