后缀数组 DC3构造法 —— 详解

  学习了后缀数组,顺便把DC3算法也看了一下,传说中可以O(n)复杂度求出文本串的height,先比较一下倍增算法和DC3算法好辣。

      DC3          倍增法

时间复杂度 O(n)(但是常数很大)     O(nlogn)(常数较小)

空间复杂度   O(n)            O(n) 

编程复杂度    较高            较低

  由于在时间复杂度上DC3的常数比较大,再加上编程复杂度比较高,所以在解决问题的时候并不是最优选择。但是学到了后缀数组还是补充一下的好点。

  DC3算法的实现:

  1:先把文本串的后缀串分成两部分,第一部分是后缀串i mod 3 == 0, 第二部分是i mod 3 != 0,然后先用基数排序对第二部分后缀串排序(按照前三个字符进行排序)。

 1 int *san = sa+n, *rn = r+n, ta=0, tb=(n+1)/3, tbc=0, i, j, p;
 2 //ta i mod 3==0的个数,tb i mod 3==1的个数, tbc imod3!=0的个数
 3 for (i=0; i<n; i++)
 4     if (i % 3)
 5         x[tbc ++] = i;
 6
 7 r[n] = r[n+1] = 0;//在文本串后面添加两个0,便于处理
 8 Sort (r+2, x, y, tbc, m);
 9 Sort (r+1, y, x, tbc, m);
10 Sort (r, x, y, tbc, m);

  然后把suffix[1]与suffix[2]数组连起来,每三个相邻的字符看做一个数,变成这个样子:

操作代码如下:

1 rn[F(y[0])] = 0;
2 for (i=1, p=1; i<tbc; i++)
3     rn[F(y[i])] = c0(r, y[i-1], y[i])?p-1:p++;
4 //#define F(x) x/3+(x%3==1?0:tb)
5 //F(x) 求原字符串suffix(i)在新串中的位置

如果p>=tbc的话,也就是说只排列前三个字符就可以区分出第二部分后缀串的顺序了,否则就要进行递归继续对第二部分的串进行排序。

1 if (p < tbc)
2     DC3 (rn, san, tbc, p);
3 else
4     for (i=0; i<tbc; i++)
5         san[rn[i]] = i;

  2:对第一部分后缀来说:

  suffix[3*i] = r[3*i] + suffix[3*i+1];

  suffix[3*j] = r[3*j] + suffix[3*j+1]; 我们已知i mod 3 == 1 的所有suffix[i]的顺序了,可以利用基数排序很快的求出第一部分后缀的顺序。

1 for (i=0; i<tbc; i++)
2     if (san[i] < tb)
3         y[ta++] = san[i]*3;
4 if (n%3 == 1)
5 //对于n%3==1时,不存在suffix[n-1] == r[n] + suffix[n];
6     y[ta++] = n - 1;
7 Sort (r, y, x, ta, m);//对mod3==0的后缀串排序

  3:第一部分后缀数组和第二部分后缀数组都排好序以后,可以对两部分后缀数组进行一次简单的归并排序,然后sa数组就完美呈现了。

 1 //#define G(x) x>=tb?(x-tb)*3+2:x*3+1
 2 //新文本串中suffix(i)在原文本串中的位置
 3 for (i=0; i<tbc; i++)
 4     c[y[i] = G(san[i])] = i;
 5 for (i=0, j=0, p=0; i<ta&&j<tbc; p++)
 6     sa[p] = c12 (y[j]%3, r, y[j], x[i])?y[j++]:x[i++];
 7 for (; j<tbc; j++)
 8     sa[p++] = y[j];
 9 for (; i<ta; i++)
10     sa[p++] = x[i];

c12就是比较第一部分与第二部分串的大小:

suffix [3*i] = r[3*i] + suffix[3*i+1];

suffix [3*j+1] = r[3*j+1] + suffix[3*j+2]; 已知suffix[3*i+1]与suffix[3*i+2]所对应的大小关系,可以比较r[3*i]与r[3*j+1]的大小得出最终结果。

suffix [3*i] = r[3*i] + suffix[3*i+1];

suffix [3*j+2] = r[3*j+2] + suffix[3*(j+1)]; 这个我们可以先比较 r[3*i] 与 r[3*j+2] 的大小,然后再比较 suffix[3*i+1] 与 suffix[3*(j+1)] ,这样就把问题转化为了第一种情况咯。

1 bool c12 (int k, int *r, int a, int b)
2 {//return 真 suffix[b]大,return false suffix[a]大
3     if (k == 1)
4         return r[a]<r[b] || (r[a]==r[b]&&c[a+1]<c[b+1]);
5     return r[a]<r[b] || (r[a]==r[b]&&c12(1, r, a+1, b+1));
6 }

对于和后缀数组相关的这两个算法,其实并没有什么难点。难理解的点就在于基数排序对数组的使用,手动模拟几遍就OK辣!

最后再附上一个完整的DC3代码

 1 #define F(x) x/3+(x%3==1?0:tb)
 2 #define G(x) x>=tb?(x-tb)*3+2:x*3+1
 3
 4 const int maxn = 110;
 5 int c[maxn*3], x[maxn*3], y[maxn*3];
 6 int sa[maxn*3], rank[maxn*3];
 7
 8 bool c0 (int *r, int a, int b)
 9 {
10     return r[a]==r[b] && r[a+1]==r[b+1] && r[a+2]==r[b+2];
11 }
12
13 bool c12 (int k, int *r, int a, int b)
14 {
15     //return 真 suffix[b]大,return false suffix[a]大
16     if (k == 1)
17         return r[a]<r[b] || (r[a]==r[b]&&c[a+1]<c[b+1]);
18     return r[a]<r[b] || (r[a]==r[b]&&c12(1, r, a+1, b+1));
19 }
20
21 void Sort (int *r, int *a, int *b, int n, int m)
22 {
23     for (int i=0; i<m; i++) c[i] = 0;
24     for (int i=0; i<n; i++) c[r[a[i]]] ++;
25     for (int i=1; i<m; i++) c[i] += c[i-1];
26     for (int i=n-1; i>=0; i--)
27         b[--c[r[a[i]]]] = a[i];
28 }
29
30 void DC3 (int *r, int *sa, int n, int m)
31 {
32     int *san = sa+n, *rn = r+n, ta=0, tb=(n+1)/3, tbc=0, i, j, p;
33     for (i=0; i<n; i++) if (i % 3)  x[tbc ++] = i;
34
35     r[n] = r[n+1] = 0;
36     Sort (r+2, x, y, tbc, m);
37     Sort (r+1, y, x, tbc, m);
38     Sort (r, x, y, tbc, m);
39
40     rn[F(y[0])] = 0;
41     for (i=1, p=1; i<tbc; i++)
42         rn[F(y[i])] = c0(r, y[i-1], y[i])?p-1:p++;
43     //rn[i] 起始位置为i的排名
44
45     if (p < tbc)
46         DC3 (rn, san, tbc, p);
47     else
48         for (i=0; i<tbc; i++)
49             san[rn[i]] = i;
50
51     for (i=0; i<tbc; i++)
52         if (san[i] < tb)
53             y[ta++] = san[i]*3;
54
55     if (n%3 == 1)
56         y[ta++] = n - 1;
57
58     Sort (r, y, x, ta, m);//对mod3==0的后缀串排序
59     for (i=0; i<tbc; i++)
60         c[y[i] = G(san[i])] = i;
61
62     for (i=0, j=0, p=0; i<ta&&j<tbc; p++)
63         sa[p] = c12 (y[j]%3, r, y[j], x[i])?y[j++]:x[i++];
64     for (; j<tbc; j++)
65         sa[p++] = y[j];
66     for (; i<ta; i++)
67         sa[p++] = x[i];
68
69     return;
70 }
时间: 2024-10-07 05:30:30

后缀数组 DC3构造法 —— 详解的相关文章

后缀数组学习笔记【详解|图】

后缀数组学习笔记[详解] 老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊! 最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了. 我不知道手跑了多少次才明白过来.其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功. 数组含义: s[ ]:输入的字符串,预处理的时候会在末尾加上一个0 sa[ ]:它的下标就是后缀排名 x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名.初始时恰好是字符串的ASCII码.字典序嘛! y[ ] = t2[ ]:它的

poj 3581 Sequence(后缀数组,离散化)详解

题目链接:http://poj.org/problem?id=3581 题目大意:给一个数列,要求将其分成三段,每段进行翻转后形成后合并成新数列,求按字典顺序最小的新数列. 思路: 注意到题目中数列a0,a2,a3...an-1, a0是最大的,因此将原数列翻转后an-1,an-2,...,a1,a0,求后缀数组, sa[0]所代表的后缀即为所求第一段翻转后的数列,注意到要分成三份,因此sa[0]<2时不可取,此时找sa[1], sa[2]看是否可取.找第一个位置后,设剩下 数列是an-1,an

c?#?中 ?s?o?c?k?e?t? ?、?T?C?P?C?l?i?e?n?t?、?T?C?P?L?i?s?t?e?n?e?r? ?用?法?详?解

Visual C#.Net网络程序开发-Socket篇 Microsoft.Net Framework为应用程序访问Internet提供了分层的.可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含丰富的类可以开发多种网络应用程序..Net类采用的分层结构允许应用程序在不同的控制级别上访问网络,开发人员可以根据需要选择针对不同的级别编制程序,这些级别几乎囊括了Internet的所有需要--从socket套接字到普通的请求/响应,更重要的是,这种分

网络双绞线4根线接法详解(水晶头RJ45)

网络双绞线4根线接法详解 一直以来很多人都认为10 Base-T 10M网络使用了网线中8条信号线之4条,而100 Base-T 100M则使用了全部8条信号线(要不怎么那么快呢?).可是作者前不久在使用一条按所谓10M直连接法(1与3.2与6交换,其余四线接外壳屏蔽)接出的网线时,意外地发现网络正以100M高速传输,百思不得其解,于是上网查阅了大量资料,加上好几台机实验验证,终于发现了事实真相,那就是,100M的双绞线与10M的标准接法完全是一样!     双绞线接头(RJ45)针脚号码定义

分支界定法详解

分支界定法是求解整数线性规划最优解的经典方法. 定义: 对有约束条件的最优化问题(其可行解为有限数)的所有可行解空间恰当地进行系统搜索,这就是分支与界定的内容.通常把全部解空间反复地分割为越来越小的子集,称为分枝:并对每个子集内的解集计算一个目标下界(对于最小值问题),这称为定界.在每次分枝后,若某个已知可行解集的目标值不能达到当前的界限,则将这个子集舍去.这样,许多子集不予考虑,这称为剪枝.这就是分枝界限法的思路. 背景: 分枝界限法可以用于求解纯整数或混合的整数规划问题.在上世纪六十年代由L

农场游戏系统开发怎么做?丰乐惠农场游戏玩法详解。

一.丰乐惠牧场系统介绍 乌鸡 9.9元 每天收益1元 15天共收益15元 北京鸭 99.9元 每天收益9元 15天共收益135元 藏猪 699.9元 每天收益67元 16天共收益1072元 白羊 1699.9元 每天收益170元 17天共收益2890元 藏獒 3499.9元 每天收益350元 18天共收益6300元 二.动态奖励 1级:5% 2-3级:2% 4-12级:1% 13-15级:2% 三.全球分红 1.当天直推有效会员3人,享受平台当天总业绩的2%(均分) 2.当天直推有效会员6人,享

POJ - 2406 Power Strings (后缀数组DC3版)

题意:求最小循环节循环的次数. 题解:这个题其实可以直接用kmp去求最小循环节,然后在用总长度除以循环节.但是因为在练后缀数组,所以写的后缀数组版本.用倍增法会超时!!所以改用DC3法.对后缀数组还不是很理解,找了很多博客也没看懂到底有些数组到底记录的是啥,但他的实现过程很好理解,等我弄懂了再来给博客加注释吧. 先求出sa数组,height数组,rank数组(因为和c++库中某个东西重了所以写成rnk数组),数组一定要开3倍.接下来从小到大枚举循环节长度 i,如果长度i的子串刚好是重复了len/

POJ2406:Power Strings(后缀数组DC3)

Description Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc" and b = "def" then a*b = "abcdef". If we think of concatenation as multiplication, exponentiation by a non-negative inte

SLAM入门之视觉里程计(6):相机标定 张正友经典标定法详解

想要从二维图像中获取到场景的三维信息,相机的内参数是必须的,在SLAM中,相机通常是提前标定好的.张正友于1998年在论文:"A Flexible New Technique fro Camera Calibration"提出了基于单平面棋盘格的相机标定方法.该方法介于传统的标定方法和自标定方法之间,使用简单实用性强,有以下优点: 不需要额外的器材,一张打印的棋盘格即可. 标定简单,相机和标定板可以任意放置. 标定的精度高. 相机的内参数 设\(P=(X,Y,Z)\)为场景中的一点,在