mirror of
https://github.com/happyflyer/wangdao-data-structure.git
synced 2026-04-29 13:11:36 +08:00
完成串
This commit is contained in:
391
ch4/README.md
391
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. 顺序存储的实现
|
||||
|
||||

|
||||
|
||||
### 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];
|
||||
$$
|
||||
|
||||
25
ch4/link.cpp
Normal file
25
ch4/link.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
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;
|
||||
}
|
||||
BIN
ch4/sequence-storage.png
Normal file
BIN
ch4/sequence-storage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 573 KiB |
21
ch4/sequence_dynamic.cpp
Normal file
21
ch4/sequence_dynamic.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
154
ch4/sequence_static.cpp
Normal file
154
ch4/sequence_static.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user