diff --git a/ch4/README.md b/ch4/README.md index 33414c3..017cafd 100644 --- a/ch4/README.md +++ b/ch4/README.md @@ -75,3 +75,394 @@ N=" " 在你的文件中,原本采用某一套编码规则 $y=f(x)$。 打开文件时,你的软件以为你采用的是另一套编码规则 $y=g(x)$。 + +## 3. 串的存储结构 + +### 3.1. 顺序存储 + +#### 3.1.1. 静态数组实现(定长顺序存储) + +```cpp +#define MAXLEN 255 +typedef struct +{ + char ch[MAXLEN]; + int length; +} SString; +``` + +#### 3.1.2. 动态数组实现(堆分配存储) + +```cpp +#define MAXLEN 255 +typedef struct +{ + char *ch; + int length; +} HString; +``` + +```cpp +HString S; +S.ch = (char *)malloc(MAXLEN * sizeof(char)); +S.length = 0; +``` + +#### 3.1.3. 顺序存储的实现 + +![顺序存储实现](sequence-storage.png) + +### 3.2. 链式存储 + +```cpp +typedef struct StringNode +{ + char ch; + struct StringNode *next; +} StringNode, *String; +``` + +```cpp +typedef struct StringNode +{ + char ch[4]; + struct StringNode *next; +} StringNode, *String; +``` + +## 4. 基本操作的实现 + +### 4.1. 求子串 + +```cpp +// 求子串 +bool SubString(SString &Sub, SString S, int pos, int len) +{ + // 子串范围越界 + if (pos + len - 1 > S.length) + { + return false; + } + for (int i = pos; i < pos + len; i++) + { + Sub.ch[i - pos + 1] = S.ch[i]; + } + Sub.length = len; + return true; +} +``` + +### 4.2. 串的比较 + +```cpp +// 比较操作 +int StrCompare(SString S, SString T) +{ + for (int i = 1; i <= S.length && i <= T.length; i++) + { + if (S.ch[i] != T.ch[i]) + { + return S.ch[i] - T.ch[i]; + } + } + // 扫描过所有字符都相同,则长度长的串更大 + return S.length - T.length; +} +``` + +### 4.3. 求串在主串中的位置 + +```cpp +// 定位操作 +int Index(SString S, SString T) +{ + int i = 1, n = StrLength(S), m = StrLength(T); + SString sub; + while (i <= n - m + 1) + { + SubString(sub, S, i, m); + if (StrCompare(sub, T) != 0) + { + ++i; + } + else + { + return i; + } + } + return 0; +} +``` + +## 5. 朴素模式匹配算法性能分析 + +```cpp +// 定位操作 +int Index2(SString S, SString T) +{ + int k = 1; // 记录主串中的匹配开始位置 + int i = k, j = 1; + while (i <= S.length && j <= T.length) + { + if (S.ch[i] == T.ch[j]) + { + ++i; + ++j; + } + else + { + k++; + i = k; + j = 1; + } + } + if (j > T.length) + { + return k; + } + else + { + return 0; + } +} +``` + +若模式串长度为 $m$,主串长度为 $n$,则: + +- 匹配成功的最好时间复杂度:$O(m)$。 +- 匹配失败的最好时间复杂度:$O(n-m-1)=O(n-m) \approx O(n)$。 + +若模式串长度为 $m$,主串长度为 $n$,则知道匹配成功/匹配失败最多需要 $(n-m+1)*m$ 次比较。 + +- 最坏时间复杂度:$O(nm)$。 + +## 6. KMP 算法 + +### 6.1. 改进思路 + +朴素模式匹配算法的缺点:当某些子串与模式串能部分匹配时,主串的扫描指针 $i$ 经常回溯,导致时间开销增加。 + +改进思路:主串指针 $i$ 不回溯,只有模式串指针 $j$ 回溯。 + +主串为 `googlgooglegooglo`,模式串为 `google`。如果 $j=k$ 时才发现匹配失败,说明 $1 \thicksim k-1$ 都匹配成功。 + +- 若当前两个字符匹配,则 `i++`,`j++`。 +- 若 $j=1$ 时发生不匹配,则应让 `i++`,而 $j$ 依然是 $1$。 +- 若 $j=2$ 时发生不匹配,则应让 $j$ 回到 $1$。 +- 若 $j=3$ 时发生不匹配,则应让 $j$ 回到 $1$。 +- 若 $j=4$ 时发生不匹配,则应让 $j$ 回到 $1$。 +- 若 $j=5$ 时发生不匹配,则应让 $j$ 回到 $2$。 +- 若 $j=6$ 时发生不匹配,则应让 $j$ 回到 $1$。 + +```cpp +int next[7]; +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | +| --- | --- | --- | --- | --- | --- | --- | +| | 0 | 1 | 1 | 1 | 2 | 1 | + +当 $j=k$ 且发现字符不匹配时,令 `j=next[k];` + +```cpp +// KMP 算法 +int Index_KMP(SString S, SString T, int next[]) +{ + int i = 1, j = 1; + while (i <= S.length && j <= T.length) + { + if (j == 0 || S.ch[i] == T.ch[j]) + { + ++i; + ++j; + } + else + { + j = next[j]; + } + } + if (j > T.length) + { + return i - T.length; + } + else + { + return 0; + } +} +``` + +### 6.2. next 数组 + +next 数组:当模式串的第 $j$ 个字符匹配失败时,令模式串跳到 $next[j]$ 再继续匹配。 + +```cpp +'abcabd' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | +| --- | --- | --- | --- | --- | --- | ----- | +| | | | | | | **3** | + +```cpp +'abababcdef' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | +| --- | --- | --- | --- | --- | --- | --- | ----- | --- | --- | --- | +| | | | | | | | **5** | | | | + +```cpp +'aaaabcd' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | ----- | --- | --- | +| | | | | | **4** | | | + +```cpp +'abcdefg' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | ----- | --- | --- | --- | --- | --- | --- | +| | **0** | | | | | | | + +$$ +next[1]=0 +$$ + +- 串的前缀:包含第一个字符,且不包含最后一个字符的字符串。 +- 串的后缀:包含最后一个字符,且不包含第一个字符的字符串。 + +当第 $j$ 个字符匹配失败,由前 $i \thicksim j-1$ 个字符组成的串记为 $S$,则: + +$$ +next[j]=S的在最长相等前后缀长度+1 +$$ + +特别地: + +$$ +next[1]=0,next[2]=1 +$$ + +```cpp +'ababaa' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | +| --- | --- | --- | --- | --- | --- | --- | +| | 0 | 1 | 1 | 2 | 3 | 4 | + +```cpp +'aaaab' +``` + +| 0 | 1 | 2 | 3 | 4 | 5 | +| --- | --- | --- | --- | --- | --- | +| | 0 | 1 | 2 | 3 | 4 | + +```cpp +// 求模式串 T 的 next 数组 +void get_next(SString T, int next[]) +{ + int i = 1, j = 0; + next[1] = 0; + while (i < T.length) + { + if (j == 0 || T.ch[i] == T.ch[j]) + { + ++i; + ++j; + // 若 pi=pj,则 next[j+1]=next[j]+1 + next[i] = j; + } + else + { + // 否则令 j=next[j],循环继续 + j = next[j]; + } + } +} +``` + +```cpp +// KMP 算法 +int Index_KMP(SString S, SString T) +{ + int i = 1, j = 1; + int next[T.length + 1]; + get_next(T, next); // 时间复杂度:$O(m)$ + while (i <= S.length && j <= T.length) // 时间复杂度:$O(n)$ + { + if (j == 0 || S.ch[i] == T.ch[j]) + { + ++i; + ++j; + } + else + { + j = next[j]; + } + } + if (j > T.length) + { + return i - T.length; + } + else + { + return 0; + } +} +``` + +KMP 算法平均时间复杂度:$O(n+m)$ + +### 6.3. KMP 算法优化 + +```cpp +'google' +``` + +| 序号 $j$ | 1 | 2 | 3 | 4 | 5 | 6 | +| ------------ | --- | --- | --- | ----- | --- | --- | +| 模式串 | g | o | o | g | l | e | +| $next[j]$ | 0 | 1 | 1 | 1 | 2 | 1 | +| $nextval[j]$ | 0 | 1 | 1 | **0** | 2 | 1 | + +```cpp +'aaaab' +``` + +| 序号 $j$ | 1 | 2 | 3 | 4 | 5 | +| ------------ | --- | ----- | ----- | ----- | --- | +| 模式串 | a | a | a | a | b | +| $next[j]$ | 0 | 1 | 2 | 3 | 4 | +| $nextval[j]$ | 0 | **0** | **0** | **0** | 4 | + +nextval 数组的求法: + +1. 先算出 next 数组。 +2. 先令 $nextval[1]=0$ +3. 循环查找 + +```cpp +for (int j = 2; j <= T.length; j++) +{ + if (T.ch[next[j]] == T.ch[j]) + { + nextval[j] = nextval[next[j]]; + } + else + { + nextval[j] = next[j]; + } +} +``` + +KMP 算法优化:当子串和模式串不匹配时, + +$$ +j = nextval[j]; +$$ diff --git a/ch4/link.cpp b/ch4/link.cpp new file mode 100644 index 0000000..e2b8a3d --- /dev/null +++ b/ch4/link.cpp @@ -0,0 +1,25 @@ +#include +#include +typedef struct StringNode +{ + char ch; + struct StringNode *next; +} StringNode, *String; + +bool InitString(String &S) +{ + S = (StringNode *)malloc(sizeof(StringNode)); + if (S == NULL) + { + return false; + } + S->next = NULL; + return true; +} + +int main(int argc, char const *argv[]) +{ + String S; + InitString(S); + return 0; +} diff --git a/ch4/sequence-storage.png b/ch4/sequence-storage.png new file mode 100644 index 0000000..9f86fe4 Binary files /dev/null and b/ch4/sequence-storage.png differ diff --git a/ch4/sequence_dynamic.cpp b/ch4/sequence_dynamic.cpp new file mode 100644 index 0000000..5ba5ec4 --- /dev/null +++ b/ch4/sequence_dynamic.cpp @@ -0,0 +1,21 @@ +#include +#include +#define MAXLEN 255 +typedef struct +{ + char *ch; + int length; +} HString; + +void InitString(HString &S) +{ + S.ch = (char *)malloc(MAXLEN * sizeof(char)); + S.length = 0; +} + +int main(int argc, char const *argv[]) +{ + HString S; + InitString(S); + return 0; +} diff --git a/ch4/sequence_static.cpp b/ch4/sequence_static.cpp new file mode 100644 index 0000000..1854838 --- /dev/null +++ b/ch4/sequence_static.cpp @@ -0,0 +1,154 @@ +#include +#define MAXLEN 255 +typedef struct +{ + char ch[MAXLEN]; + int length; +} SString; + +void InitString(SString &S) +{ + S.length = 0; +} + +// 求串长 +int StrLength(SString S) +{ + return S.length; +} + +// 求子串 +bool SubString(SString &Sub, SString S, int pos, int len) +{ + // 子串范围越界 + if (pos + len - 1 > S.length) + { + return false; + } + for (int i = pos; i < pos + len; i++) + { + Sub.ch[i - pos + 1] = S.ch[i]; + } + Sub.length = len; + return true; +} + +// 比较操作 +int StrCompare(SString S, SString T) +{ + for (int i = 1; i <= S.length && i <= T.length; i++) + { + if (S.ch[i] != T.ch[i]) + { + return S.ch[i] - T.ch[i]; + } + } + // 扫描过所有字符都相同,则长度长的串更大 + return S.length - T.length; +} + +// 定位操作 +int Index(SString S, SString T) +{ + int i = 1, n = StrLength(S), m = StrLength(T); + SString sub; + while (i <= n - m + 1) + { + SubString(sub, S, i, m); + if (StrCompare(sub, T) != 0) + { + ++i; + } + else + { + return i; + } + } + return 0; +} + +// 定位操作 +int Index2(SString S, SString T) +{ + int k = 1; // 记录主串中的匹配开始位置 + int i = k, j = 1; + while (i <= S.length && j <= T.length) + { + if (S.ch[i] == T.ch[j]) + { + ++i; + ++j; + } + else + { + k++; + i = k; + j = 1; + } + } + if (j > T.length) + { + return k; + } + else + { + return 0; + } +} + +// 求模式串 T 的 next 数组 +void get_next(SString T, int next[]) +{ + int i = 1, j = 0; + next[1] = 0; + while (i < T.length) + { + if (j == 0 || T.ch[i] == T.ch[j]) + { + ++i; + ++j; + // 若 pi=pj,则 next[j+1]=next[j]+1 + next[i] = j; + } + else + { + // 否则令 j=next[j],循环继续 + j = next[j]; + } + } +} + +// KMP 算法 +int Index_KMP(SString S, SString T) +{ + int i = 1, j = 1; + int next[T.length + 1]; + get_next(T, next); // 时间复杂度:$O(m)$ + while (i <= S.length && j <= T.length) // 时间复杂度:$O(n)$ + { + if (j == 0 || S.ch[i] == T.ch[j]) + { + ++i; + ++j; + } + else + { + j = next[j]; + } + } + if (j > T.length) + { + return i - T.length; + } + else + { + return 0; + } +} + +int main(int argc, char const *argv[]) +{ + SString S; + InitString(S); + return 0; +}