- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
经过大量的阅读之后,我弄清楚了后缀数组和LCP数组代表什么。
后缀数组:表示数组每个后缀的_lexicographic等级。
LCP数组:按字典顺序对两个连续后缀之间的最大长度前缀匹配进行匹配。
几天以来,我一直在努力理解后缀数组和LCP算法的工作原理。
这是代码,摘自Codeforces:
/*
Suffix array O(n lg^2 n)
LCP table O(n)
*/
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define REP(i, n) for (int i = 0; i < (int)(n); ++i)
namespace SuffixArray
{
const int MAXN = 1 << 21;
char * S;
int N, gap;
int sa[MAXN], pos[MAXN], tmp[MAXN], lcp[MAXN];
bool sufCmp(int i, int j)
{
if (pos[i] != pos[j])
return pos[i] < pos[j];
i += gap;
j += gap;
return (i < N && j < N) ? pos[i] < pos[j] : i > j;
}
void buildSA()
{
N = strlen(S);
REP(i, N) sa[i] = i, pos[i] = S[i];
for (gap = 1;; gap *= 2)
{
sort(sa, sa + N, sufCmp);
REP(i, N - 1) tmp[i + 1] = tmp[i] + sufCmp(sa[i], sa[i + 1]);
REP(i, N) pos[sa[i]] = tmp[i];
if (tmp[N - 1] == N - 1) break;
}
}
void buildLCP()
{
for (int i = 0, k = 0; i < N; ++i) if (pos[i] != N - 1)
{
for (int j = sa[pos[i] + 1]; S[i + k] == S[j + k];)
++k;
lcp[pos[i]] = k;
if (k)--k;
}
}
} // end namespace SuffixArray
最佳答案
总览
这是一种用于后缀数组构造的O(n log n)算法(或者,如果不是::sort
而是使用2遍存储桶排序,则为O(n log n)算法)。
它首先对原始字符串S
排序2克(*),然后对4克,然后对8克等等进行排序,因此在第i次迭代中,我们对2i克进行了排序。显然,这样的迭代最多只能有log2(n)个,其诀窍是,通过确保在O(1)中完成两个2i-gram的每个比较,可以方便地在第i步中对2i-gram进行排序。时间(而不是O(2i)时间)。
它是如何做到的?好吧,在第一个迭代中对2克(又称双字母组)进行排序,然后执行所谓的词典重命名。这意味着它将创建一个新数组(长度为n
),该数组为每个bigram存储其在bigram排序中的排名。
词典重命名示例:假设我们有一个排序的列表,其中包含一些双字母组{'ab','ab','ca','cd','cd','ea'}
。然后我们从左到右分配等级(即字典名称),从等级0开始并在遇到新的双字母组变化时递增等级。因此,我们分配的等级如下:
ab : 0
ab : 0 [no change to previous]
ca : 1 [increment because different from previous]
cd : 2 [increment because different from previous]
cd : 2 [no change to previous]
ea : 3 [increment because different from previous]
i
都有两个步骤:n-1
相同,则每个2i-gram必须被赋予其自己的独特字典名称。sa[]
是我们正在构建的后缀数组。 pos[]
是排名查找表(即,它包含词典名称),具体地说,pos[k]
包含上一步的k
-th m-gram的词典名称。 tmp[]
是用于帮助创建pos[]
的辅助数组。void buildSA()
{
N = strlen(S);
/* This is a loop that initializes sa[] and pos[].
For sa[] we assume the order the suffixes have
in the given string. For pos[] we set the lexicographic
rank of each 1-gram using the characters themselves.
That makes sense, right? */
REP(i, N) sa[i] = i, pos[i] = S[i];
/* Gap is the length of the m-gram in each step, divided by 2.
We start with 2-grams, so gap is 1 initially. It then increases
to 2, 4, 8 and so on. */
for (gap = 1;; gap *= 2)
{
/* We sort by (gap*2)-grams: */
sort(sa, sa + N, sufCmp);
/* We compute the lexicographic rank of each m-gram
that we have sorted above. Notice how the rank is computed
by comparing each n-gram at position i with its
neighbor at i+1. If they are identical, the comparison
yields 0, so the rank does not increase. Otherwise the
comparison yields 1, so the rank increases by 1. */
REP(i, N - 1) tmp[i + 1] = tmp[i] + sufCmp(sa[i], sa[i + 1]);
/* tmp contains the rank by position. Now we map this
into pos, so that in the next step we can look it
up per m-gram, rather than by position. */
REP(i, N) pos[sa[i]] = tmp[i];
/* If the largest lexicographic name generated is
n-1, we are finished, because this means all
m-grams must have been different. */
if (tmp[N - 1] == N - 1) break;
}
}
sufCmp
用于按字典顺序比较两个(2 * gap)-gram。因此,在第一个迭代中,它比较双字母组,在第二个迭代中,它比较4克,然后是8克,依此类推。这是由gap
控制的,它是一个全局变量。sufCmp
的一个简单的实现是这样的:bool sufCmp(int i, int j)
{
int pos_i = sa[i];
int pos_j = sa[j];
int end_i = pos_i + 2*gap;
int end_j = pos_j + 2*gap;
if (end_i > N)
end_i = N;
if (end_j > N)
end_j = N;
while (i < end_i && j < end_j)
{
if (S[pos_i] != S[pos_j])
return S[pos_i] < S[pos_j];
pos_i += 1;
pos_j += 1;
}
return (pos_i < N && pos_j < N) ? S[pos_i] < S[pos_j] : pos_i > pos_j;
}
pos_i:=sa[i]
开头的(2 * gap)-gram与在第j个后缀pos_j:=sa[j]
开头找到的gram。它会逐个字符地比较它们,即比较S[pos_i]
和S[pos_j]
,然后S[pos_i+1]
和S[pos_j+1]
等等。只要字符相同,它就会继续。一旦它们不同,如果第i个后缀的字符小于第j个后缀的字符,则返回1,否则返回0。 (请注意,返回return a<b
的函数中的int
表示如果条件为true,则返回1,如果条件为false,则返回0。)pos_i
或pos_j
都将到达N
,即使到该点为止的所有字符都相同。如果第i个后缀在末尾,则返回1;如果第j个后缀在末尾,则返回0。这是正确的,因为如果所有字符都相同,则较短的字符在字典上较小。如果pos_i
已到达结尾,则第i个后缀必须比第j个后缀短。bool sufCmp(int i, int j)
{
if (pos[i] != pos[j])
return pos[i] < pos[j];
i += gap;
j += gap;
return (i < N && j < N) ? pos[i] < pos[j] : i > j;
}
S[i]
和S[j]
,而是检查了第i和第j后缀的词典顺序。在以前的迭代中为间隙图计算了辞书等级。因此,如果是pos[i] < pos[j]
,则第i个后缀sa[i]
必须以一个间隔词开始,该词在字典上小于sa[j]
开头的差距词。换句话说,仅通过查找pos[i]
和pos[j]
并进行比较,我们就比较了两个后缀的第一个空格字符。pos[i+gap]
和pos[j+gap]
。这与比较(2 * gap)-gram的下一个间隙字符(即下半部分)相同。如果等级再次相同,则两个(2 * gap)-gram相同,因此返回0。否则,如果第i个后缀小于第j个后缀,则返回1。abcxabcd
。为此需要三个迭代来生成后缀数组。在每次迭代中,我将显示S
(字符串),sa
(后缀数组的当前状态)以及tmp
和pos
,它们代表词典名称。S abcxabcd
sa 01234567
pos abcxabcd
sa
进行排序:sa 04156273
tmp 00112345
sa
中的位置将其映射为pos
:sa 04156273
tmp 00112345
pos 01350124
pos
的方法是这样的:从左到右浏览sa
,并使用该条目在pos
中定义索引。使用tmp
中的相应条目来定义该索引的值。因此pos[0]:=0
,pos[4]:=0
,pos[1]:=1
,pos[5]:=1
,pos[6]:=2
等。索引来自sa
,值来自tmp
。)sa
进行排序,然后再次查看pos
中的二元组(每个二元组代表原始字符串的两个二元组的序列)。sa 04516273
sa
相比,1 5的位置是如何切换的。它以前是15,现在是51。这是因为在上一次迭代期间,pos[1]
的bigram和pos[5]
的bigram过去是相同的(两者都是bc
),但是现在pos[5]
的bigram是12
,而bigram则是pos[1]
是13
。因此,位置5
在位置1
之前。这是由于以下事实:词典名称现在每个都代表原始字符串的双字母:pos[5]
代表bc
,而pos[6]
代表“cd”。因此,它们一起表示bcd
,而pos[1]
表示bc
,而pos[2]
表示cx
,因此它们一起表示bcx
,在字典上确实比bcd
大。sa
并比较pos
中相应的双字母组来生成词典名称:tmp 00123456
pos
中的相应二元组都是01
。其余部分是严格增加的整数序列,因为pos
中的所有其他双字母组都是唯一的。pos
的映射(从sa
获取索引,从tmp
获取值):sa 04516273
tmp 00123456
pos 02460135
sa
进行排序,并使用pos
的二元组(一如既往),现在每个04
代表原始字符串的4个二元组的序列。sa 40516273
40
已变为pos[0]
。这是因为02
的bigram是pos[4]
,而01
的bigram是abcx
,后者在字典上明显更小。深层原因是这两个分别代表abcd
和7
。tmp 01234567
n-1
,即sort
。如此,我们完成了,因为排序现在基于所有不同的m-gram。即使我们继续,排序顺序也不会改变。std::sort
(或S=abcde
)。这意味着它是一种比较排序,在最坏的情况下,每次迭代都需要O(n log n)时间。由于在最坏的情况下存在log n次迭代,因此这使其成为O(n(log n)2)时间算法。但是,可以使用两次遍历的存储桶排序来执行排序,因为我们用于排序比较的键(即上一步的词典名称)形成了一个递增的整数序列。因此,可以将其改进为用于后缀排序的实际O(n log n)时间算法。ab
是字符串时,那么bc
,cd
,de
和S
是abcd
的2克。同样,bcde
和m
是4克。通常,m-gram(对于正整数m)是S
连续字符的序列。 1-gram也称为unigram,2-gram称为bigrams,3-gram称为trigram。有些人会继续使用四卦,五芒星等。i
的S
后缀是S
的(n-i)-gram。同样,每个m-gram(对于任何m-gram)都是ojit_code后缀之一的前缀。因此,对m-gram进行排序(对于一个尽可能大的m)可以是对后缀进行排序的第一步。
关于c++ - 后缀数组算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17761704/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!