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

    1 背景

      后缀数组是处理字符串问题的有效算法,如最长公共字串,最长回文字串,重复次数最多的字串等等。所以学好它,对于以后处理处理字符串是很有帮助的。在网上找了下,发现对后缀数组讲的比较容易理解和清楚的是罗穗骞写的关于“后缀数组——处理字符串的有力工具”的论文,这篇文章主要总结下该论文中的主要内容。

    2 定义

      后缀:后缀是指从某个位置 i 开始到整个串末尾结束的一个特殊子串。字符串r 的从第 i 个字符开始的后缀表示为Suffix(i) , 也 就 是Suffix(i)=r[i..len(r)]。

      后缀数组:后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1],SA[2],......,SA[n],并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。也就是将 S 的 n 个后缀的后缀从小到大进行排序之后把排好序的开头位置顺次放入 SA 中。

      名次数组名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排列的“名次”。

      height 数组:定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。

    3 性质

      (a)后缀数组和名次数组为互逆运算,后缀数组表示排名第几的是谁,名次数组表示谁排名第几,即sa[rank[i]] = i, rank[sa[i]] = i。

      (b)定 义h[i]=height[rank[i]],也就是 suffix(i)和在它前一名的后缀的最长公共前缀。h[i]有如下性质:

    h[i]≥h[i-1]-1

      证明:设 suffix(k)是排在 suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。那么 suffix(k+1)将排在 suffix(i)的前面(这里要求 h[i-1]>1,如果h[i-1]≤1,原式显然成立)并且 suffix(k+1)和 suffix(i)的最长公共前缀是h[i-1]-1,所以 suffix(i)和在它前一名的后缀的最长公共前缀至少是 h[i-1]-1。

    4 实现

      求解后缀数组和名次数组:用倍增的方法对每个字符开始的长度为 2k 的子字符串进行排序,求出排名,即 rank 值。k 从 0 开始,每次加 1,当 2k 大于 n 以后,每个字符开始的长度为 2k 的子字符串便相当于所有的后缀。并且这些子字符串都一定已经比较出大小,即 rank 值中没有相同的值,那么此时的 rank 值就是最后的结果。每一次排序都利用上次长度为 2k-1 的字符串的 rank 值,那么长度为 2k 的字符串就可以用两个长度为 2k-1 的字符串的排名作为关键字表示,然后进行基数排序,便得出了长度为 2k 的字符串的 rank 值。以字符串“aabaaaab”为例,整个过程如下图所示。其中 x、y 是表示长度为 2k 的字符串的两个关键字 。

      计算数组height:可以令i从0到n-1依次求出h[i]。初始化k = 0,k表示h[i]的值。具体分下面两种情况

      (1)如果rank[i] == 0,说明suffix(i)排名第一,则h[i] = 0,此时k也更新为0。

      (2)如果rank[i] > 0, 求出排在suffix(i)前一名的下标为j = sa[rank[i] - 1]。而此时的k表示的是h[i - 1]的值,根据前面的性质b,当前的h[i] >= h[i - 1] - 1,即h[i] >= k - 1。为了确定当前的h[i],则只需要从suffix(i)和suffix(j)的第k位开始比较(通过h[i],省去了前面的k-1次比较,类似与dp的思想)来确定h[i]。

      具体实现:

     1 #include <stdio.h>
     2 #include <string.h>
     3 
     4 #define MAXSIZE 400005
     5 #define min(a, b) a < b ? a : b
     6 int sa[MAXSIZE], rank[MAXSIZE], height[MAXSIZE],temp[MAXSIZE];
     7 
     8 int cmp(int *r, int a, int b, int l)
     9 {
    10     return (r[a] == r[b] && r[a + l] == r[b + l]);
    11 }
    12 
    13 int get_suffix(char *str, int n)
    14 {
    15     int *t, *x = rank, *y = temp, ws[MAXSIZE], wv[MAXSIZE], i, j, k, m;    
    16 
    17     //对字符串中的字符进行基数排序
    18     m = 128;
    19     for (i = 0; i < m; i++)    
    20         ws[i] = 0;
    21     for (i = 0; i < n; i ++)    
    22         ws[x[i] = str[i]]++;
    23     for (i = 1; i < m; i ++)
    24         ws[i] += ws[i - 1];
    25     for (i = n - 1; i >= 0; i--)
    26         sa[--ws[x[i]]] = i;
    27 
    28     for (j = 1, k = 1; k < n; j *= 2)
    29     {
    30         //对第二关键字排序
    31         k = 0;
    32         for (i = n - j; i < n; i++)    
    33             y[k++] = i;
    34         for (i = 0; i < n; i ++)
    35             if (sa[i] >= j)
    36                 y[k++] = sa[i] - j;
    37         //对第一关键字排序
    38         for (i = 0; i < n; i ++)
    39             wv[i] = x[y[i]];
    40         for (i = 0; i < m; i ++)
    41             ws[i] = 0;
    42         for (i = 0; i < n; i ++)
    43             ws[wv[i]]++;
    44         for (i = 1; i < m; i ++)
    45             ws[i] += ws[i - 1];
    46         for (i = n - 1; i >= 0; i --)
    47             sa[--ws[wv[i]]] = y[i];
    48         //对x数组进行修正
    49         for (t = y, y = x, x = t, k = 1, x[sa[0]] = 0, i = 1; i < n; i ++)
    50             x[sa[i]] = cmp(y, sa[i], sa[i - 1], j) ? k - 1 : k++;
    51         m = k;
    52     }
    53     //计算rank
    54     for (i = 0; i < n; i ++)
    55         rank[sa[i]] = i;
    56     //计算height
    57     k = 0;
    58     for (i = 0; i < n; i ++)
    59     {
    60         if (rank[i] == 0)
    61         {
    62             height[0] = k = 0;
    63             continue;
    64         }
    65         if (k > 0)
    66             k--;
    67         j = sa[rank[i] - 1];
    68         while (j + k < n && i + k < n && str[j + k] == str[i + k])
    69             k++;
    70         height[rank[i]] = k;
    71     }
    72 }

     参考:后缀数组——处理字符串的有力工具  罗穗骞

  • 相关阅读:
    light-rtc: 理念与实践
    浅谈 WebRTC 的 Audio 在进入 Encoder 之前的处理流程
    倍频程与钢琴调式的距离
    实战排查|为什么遮挡推流摄像头,会导致播放绿屏?
    深入浅出 WebRTC AEC(声学回声消除)
    揭秘 VMAF 视频质量评测标准
    绕过CDN查找真实IP方法笔记
    Linux安全加固
    Windows安全加固
    常见端口渗透总结
  • 原文地址:https://www.cnblogs.com/chengxuyuancc/p/3306963.html
Copyright © 2011-2022 走看看