一、字符串
和线性表类似,字符串也有顺序存储和链式存储两种存储方式
1.顺序存储
#define maxsize 255
typedef struct {
char ch[maxsize];
int length;
}sstring;
这种定义方式是静态的,在编译时刻就确定了字符串的空间的大小,但在很多情况下,事先确定大小是不方便的,也不方便后续处理。最好是根据实际需求,在程序执行过程中动态分配和释放字符数组空间。有一个称为堆的自由存储区,可以为新产生的字符串动态分配存储空间。这种存储方式也叫字符串的堆式顺序存储结构
typedef struct {
char* ch;
int length;
}Hstring;
2.链式存储
#define chunksize 80
typedef struct chunk{//可由用户定义块大小
char ch[chunksize];//每个块的大小
struct chunk* next;
}chunk;
typedef struct {
chunk* head, * tail;//头指针和尾指针
int length;
};
在链式存储中,结点的大小选择决定着问题的处理效率,应该根据问题要求确定块大小。
字符串的链式存储在串的链接等方面有一定方便之处,但总的来说,不如顺序存储结构来的方便灵活,所以我们较多还是使用顺序存储。
字符串的简单应用没有什么大问题,在算法上的使用较多为匹配问题,比如给一个子字符串,求其在主字符串中从指定位置开始,出现的第一次匹配位置。下面我们来看看字符串的两种匹配算法。
二、BF匹配算法
匹配模式不一定是从主串的第一个位置开始,可以指定开始位置pos.
分别利用指针i和j指示两个串中当前比较位置,i初值为pos-1(指定位置为pos则下标为pos-1),j初值为0;
//返回模式串t在主串s中第pos个字符开始第一次出现的位置,若不存在返回0
int BF(string s, string t, int pos) {
int i = pos - 1, j = 0;//初始化
while (i < s.length() && j < t.length()) {//两个字符串均为比较到末尾
if (s[i] == t[j]) { i++, j++; }//当前字符相同,继续比较
else {
i = i - j + 1, j = 0;//指针回退重新开始匹配
}
}
if (j == t.length()) return i - t.length();//匹配成功
else return 0;//匹配失败
}
bf算法在最好的情况下平均时间复杂度为O(n+m);在最坏的情况下(直到最后才匹配成功)时间复杂度为O(n*m);
bf算法直观简单,但每次匹配失败时,需要回溯指针,时间复杂度高
三、KMP匹配算法
emmm,kmp这个玩意很难说清楚,但理解了就很简单,这玩意只可意会不可言传。
对于kmp算法,最重要的理解其next数组,next是一个用来存储模式串的对应长度时的前缀和后缀的最大相同长度的数组
next数组的求解
kmp有很多种写法,这里以next[0]=0开始来写
比如abaabcac, next[0]就在a中找,则为0; next[1]就在ab中找,也为0; next[2]在aba中找,a和a相同,则next[2]为1; 以此类推。
求next数组其实相当于拿模式串来自己匹配自己,使用i应该从1开始
kmp算法中当字符不匹配,改变j时其实是在缩用来匹配的模式串,缩到何处根据next来确定
如上述字符串,当a和c不匹配时,kmp算法不需要像bf那样把模式串全部回溯,而是根据next回到最大匹配处,即a和c不匹配时,模式串回到ab继续开始匹配。next数组也是用一样的思路来求。
//求next数组模板
void getNext(int* next, string& s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j - 1]; // 向前回溯
}
if (s[i] == s[j]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
求得next数组后就可以进行字符串的匹配咯。
组合起来就是kmp模板
//kmp模板
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j - 1]; // 向前回溯
}
if (s[i] == s[j]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
//从指定位置pos开始,找到第一次的匹配位置下标,若没有匹配的位置,返回0
int kmp(string s, string t, int pos) {
int* next = new int[t.size()];
getNext(next, t);
int j = 0;
for (int i = pos-1; i < s.length(); i++) {// 注意i从pos-1开始
while (j > 0 && s[i] != t[j]) {// 不匹配
j = next[j - 1];// j 寻找之前最大匹配的位置
}
if (s[i] == t[j]) {// 匹配,j和i都向后移动
j++;
}
if (j == t.length()) // 主串s里出现了模式串t,返回匹配的位置下标
return i - t.length()+1;
}
return 0;主串中不包含模式串,返回0
}