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] .....就可以了

  • 相关阅读:
    【leetcode】1215.Stepping Numbers
    【leetcode】1214.Two Sum BSTs
    【leetcode】1213.Intersection of Three Sorted Arrays
    【leetcode】1210. Minimum Moves to Reach Target with Rotations
    【leetcode】1209. Remove All Adjacent Duplicates in String II
    【leetcode】1208. Get Equal Substrings Within Budget
    【leetcode】1207. Unique Number of Occurrences
    【leetcode】689. Maximum Sum of 3 Non-Overlapping Subarrays
    【leetcode】LCP 3. Programmable Robot
    【leetcode】LCP 1. Guess Numbers
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4604444.html
Copyright © 2011-2022 走看看