考纲【字符串匹配模式】
串的定义和实现
串的定义
串(string)是由零个或多个字符组成的有限序列,一般记为
S=‘a₁a₂...aₙ’(n≥0)
aᵢ可以是字母,数字,或者其他字符;串中字符个数称为串的长度。n=0时,称为空串,用空集表示。串中任意多个连续的字符组成的子序列称为该串的子串,包含子串的串称为主串。
串的基本操作
bool StrAssign(String& T, String chars);//赋值 bool StrCopy(String& T, String S);//复制 bool StrEmpty(String T);//判空 bool StrCompare(String T, String S);//串比较 int StrLength(String T);//求串长 bool SubString(String& Sub, String S, int pos, int len);//求子串 bool Concat(String& T, String S1, String S2);//串链接 int Index(String T,String S);//定位,定位主串S中存在与T串值相同的子串并记录第一次出现的位置 bool ClearString(String& S);//清空 bool DestoryString(String& S);//销毁
串的存储结构
#define MaxLen 255 typedef struct String //定长顺序存储 { char ch[MaxLen]; int length; }SString; typedef struct HString //堆分配存储 { char *ch; int length; }H_String;
在C语言中,存在一个称为堆的自由存储区,并用malloc()和free()函数来完成动态存储管理,C++中使用new和delete也是在堆内存上。
块链的存储表示
类似于线性表的链式存储结构,也可采用链表的方式存储串值。由于串的特殊性,具体实现每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构。
串的模式匹配
简单的模式匹配算法
模式匹配是指在主串找到与模式串相同的子串,并返回其位置。下列采用定长顺序存储结构
int Index(String S, String T)//定位,定位主串S中存在与T串值相同的子串并记录第一次出现的位置 { int i = 1, j = 1; while (i <= S.length && j <= T.length) { if (S.ch[i] == T.ch[j]) { ++i; ++j; //继续比较 } else { i = i - j + 2; j = 1; //指针后退 重新开始匹配 } } if (j > T.length) return i - T.length; else return 0; }
模式串‘abcac'和主串匹配过程: i j
第一趟:a b a d c a b c a c b a b
a b c
第二趟: a b a d c a b c a c b a b
a
第三趟: a b a d c a b c a c b a b
a b c a c
第四趟: a b a d c a b c a c b a b
a
第五趟: a b a d c a b c a c b a b
a
第六趟: a b a d c a b c a c b a b
a b c a c j
算法思想:从主串S的第一个字符起,与模式串T的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起,在重新和模式串T比较;
最坏时间复杂度O(nm)
串的模式匹配算法——KMP算法
1.KMP算法的原理
- 'a'的前缀和后缀都为空集,最长相等前后缀长度为0
- 'ab'的前缀为{a},后缀为{b},{a}∩{b} = Ø,最长相等前后缀长度为0
- 'aba'的前缀为{a,ab},后缀为{ba,a},{a,ab}∩{ba,a} ={a},最长相等前后缀长度为1
- 'abab'前缀{a,ab,aba},后缀{b,ab,bab},最长交集{ab},长度为2
- 'ababa'前缀{a,ba,aba,abab},后缀{a,ba,aba,baba},最长交集{aba},长度3
因此,模式串'ababa'的部分匹配值为00123
2.next数组手动算法
在实际的匹配过程中,模式串在内存中是不会滑动的,发生变化的是指针,上面是手动模拟KMP算法过程;next[j]数组的含义是当模式串的第j个字符失配时,跳到next[j]位置继续比较。
编号 1 2 3 4 5 S a b c a c next 0 1 1 1 2 上述KMP算法的举例中,都假设串的编号是从1开始的;若串的编号从0开始,则next数组需要整体减1
3.next数组的推理公式
0 j=1 next[j] max{k|1<k<j且'p₁....pₖ₋₁'='pⱼ₋ₖ₊₁....pⱼ₋₁'} 当此集合不为空时 1 其它情况 4.KMP算法的实现
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; next[i] = j;//若pi = pj,则next[j+1] = next[j]+1 } else j = next[j]; //否则 j = next[j] 循环继续 } } 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; }
KMP算法时间复杂度O(n+m)
KMP算法的进一步优化
void get_nextval(SString T, int nextval[]) { int i = 1, j = 0; nextval[1] = 0; while (i < T.length) { if (T.ch[i] == T.ch[j] || j == 0) { ++i; ++j; if (T.ch[i] != T.ch[j]) nextval[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } }