@ljt12138
2017-02-11T14:33:22.000000Z
字数 4547
阅读 1502
算法
摘要:本文主要给出字符串的定义和若干简单组合性质的证明,并简要分析字符串相关算法。
定义1.1: 由有限字母表 中元素构成的数组P[1..m]称为一个字符串,m称为字符串的长度,记作 。记 是该字母表下所有有限字符串构成的的集合。如果 ,记
定义1.2: 对于两个字符串A, S,如果 且 ,称A为S的前缀,记作 ,或 。对于两个字符串B, S,如果 且 ,称B为S的后缀,记作 ,或 。
引理1.1: 都是等价关系。
证明:显然。
引理1.2(前缀、后缀重叠引理): 对于字符串S的两个前缀,,对于两个后缀
证明:显然。
定义1.3:两个字符串的连接。记
引理1.3: 群 同态于非负整数加群。
证明:显然。
定义2.1: 对于一个字符串S,如果 ,称为S的一个border。S所有border中长度最长且不为其本身的称为其最长非平凡border,记作 。
引理2.1(border的传递性): A是B的border,B是C的border,则A是C的border。
证明:显然。
引理2.2: 是S的第二长非平凡border。由此可以知道, 是S的第n长非平凡border。
证明:第一部分用反证法,设 ,如果存在border满足 ,由于引理1.2,。根据定义,, 由于A为S的border,有 。由于,因而,所以。由于 ,可知 是A的border,与 矛盾。第二部分用归纳法,这里省略。
定义2.2: 如果S的一个前缀,满足,称i是S的周期。
引理2.3: i是S非平凡的周期 是S的非平凡border。
证明:
1. 考虑与S相交的形态,设k为满足的最小正整数,则 ,设 则显然。由于, 因而T为S的border;
2. border长度长于字符串一半时:
S_i: |--s4--|
S_i: |--s2--|
Border: |---------|
S: |----------------|
Border:|---------|
S_i: |--s1--|
S_i: |--s3--|
Border: |-s2-|
S: |----------------|
Border:|-s1-|
P: |-S1-|------|-s2-|------|
P: |-----------|-----------|
定理2.4(弱周期定理): p和q是S的周期,满足 则 为S的周期。
证明:令,根据更相减损术的证明我们知道,只需要证明q-p为S的周期即可。由引理2.2可以知道 是S的border。设,由于,故两个周期不能都大于字符串一半长,两个border不能都小于字符串一半长。由于是的border,那么;同理是的border,那么。由于,则。根据定义可以知道是S的border,根据引理2.3,d是S的周期。
弱周期定理是一个强大的定理,在很多证明中我们都要用到,请读者认真理解。
推论2.5: 不小于字符串一半长的border长度成等差数列;小于字符串一半长的周期长度成等差数列,且公差等于最小的周期长。
证明:两命题等价,我们只证明第二个。取最小的周期长,则对于任意一个周期满足,根据弱周期定理,也是周期,因为i是最小周期,,则。接着我们只需要证明,都有k是周期,这是显然的,因为只需要将重复次便可以得到,其显然为周期。
定理2.6: 一个字符串所有的border可以被划分成段等差数列;一个字符串所有的周期可以被划分成段等差数列。
证明:考虑将字符串|S|所有border按长度划分为。根据推论2.5,最后一段中是正确的。对于,考虑长度在的border长为,则的长度大于一半的border,成等差数列,第一个命题得证。根据引理2.3不难证明第二个命题。
算法2.1:快速计算前缀的
我们定义为,下面的算法可以计算出所有前缀的最长非平凡border。
void calc_pi(int pi[], char str[], int len)
{
pi[1] = 0;
int j = 0;
for (int i = 2; i <= len; i++) {
while (j && str[j+1] != str[i]) i = pi[i];
if (str[j+1] == str[i]) ++j;
pi[i] = j;
}
}
正确性:核心在于算法第6行,即如果的border且不是S的border,那么都不是S的border。我们用反证法,假设是S的border,则是的border,也是的border,和的定义矛盾。
时间复杂度:由于j的增加只出现在第7行,而6行的while
循环则会使j减小,由于增加最多为|S|,因而while循环运行次数为,总复杂度为。
定义2.3: 对于两个字符串P, S,如果S存在一个后缀满足,称P在的位置匹配了S。
定理2.4: 如果,则P对S所有匹配的位置成一等差数列。
证明:我们取最小的匹配位置i和第二小匹配位置j,对于之后任意一个匹配位置k,由于任意两个匹配都相交。
S: |--------------------|
P1: i----P1-----|
P2: j----P2-----|
P3: k----P3-----|
由于P1是的border,因而j-i是的周期;由于P1是的border,k-i是的周期,因而也是的周期。根据弱周期引理, 也是的周期。如果j-i不是的最小周期,设最小周期为u,则有最长border Px, :
S: |--------------------|
P1: i----P1-----|
Px: ------Px-----|
Px2: |-----Px2---|
Px: |------Px-----
P2: j----P2-----|
P3: k----P3-----|
观察到Px相交的区域包含一个也构成一个匹配,这与P2为第二小的匹配位置矛盾。因而j-i是的最小周期。则即。
下面我们只需要说明位置可以匹配:
S: |--------------------|
P1: i----P1-----|
P2: j----P2-----|
P3: p----P3-----|
P4: k----P4-----|
...
我们在所有满足的位置构造Px,直到最后一个匹配。以P4为最后一个为例,我们需要证明P3是一个匹配。由于P2是的border,k-j也是其周期,则k-j也是的周期,因而P2是的border,P3因而也是一个匹配。用类似的构造法可以证明原命题。
算法2.2:KMP字符串匹配算法
void kmp(char S[], int Sl, char P[], int Pl, int pi_P[])
{ // pi_P[]可以由算法2.1算出
int j = 0;
for (int i = 1; i <= S1; i++) {
while (j && P[j+1] != S[i]) j = pi_P[j];
if (P[j+1] == S[i]) ++j;
if (j == Pl) {
printf("Matched Successfully on %d\n", i-Pl+1);
j = pi_P[j]; // find again
}
}
}
算法正确性和复杂度的证明类似于算法2.1,请读者类比2.1证明之。