数据结构学习笔记三
数组与结构(2)
上一篇笔记讲的是ADT矩阵的存储和相应算法,今天是本章的第二块内容,ADT字符串。字符串是一种最基本的数据类型,它的存储表示和相应算法在其他计算机课程中也是至关重要的。
接上一篇:
二、ADT字符串
1、存储结构
顺序存储结构
字符串的顺序存储结构是指用固定长度的数组来存储串中的字符序列。这很方便,数组有随机访问元素的特点,而字符串也需要这样的特性,所以用数组来存储串,再合适不过了。但是,还有一个小问题,就是如何确定串的长度?通常来说有三种做法:
1)把串的长度保存在数组的第一个元素中。
2)在串的末尾加一个结束符号,作为串结束的标志。
3)把串的长度保存在数组的最后一个元素中。
在C语言和C++语言中,都是采用第二种方法,在串的末尾自动添加一个结束符号:\0,作为串结束的标志。
串的存储结构其实没什么太多的内容,重要的是在串上的各种算法,所以对于串的存储结构的讨论到此为止,接下来我们来看一下在ADT串上的各种操作。
2、存储结构上的操作算法
ADT串上的操作有很多,我不想一一列举出来,有些操作实现起来是非常简单的,我不想做过多的讨论。
在ADT串的操作上值得一说的就是a、模式匹配算法和b、统计字符串中各个不同字符的出现频率。
下面我们就着重讨论这两个算法:
a、模式匹配
模式匹配是有很多算法的,但其中最基本的有两个:BF简单模式匹配算法和KMP算法(快速匹配)
设:S是主串,T是模式,n1是主串S的长度,n2是模式串T的长度。
简单模式匹配:(BF算法)
简单模式匹配就是最朴素的做法,将模式串和主串中的每个字符都做一次比较,找出相匹配的部分。该算法需要设置两个指针i、j,分别指向S和T的当前正在比较的字符,具体过程如下:(i,j初始时都是0,数组的起始位置是0)
①检查是否满足i<n1&&j<n2,若不满足,转到⑤
②比较S[i]和T[j]是否相同,若不同,转到④,否则继续
③i++,j++,转到①
④令i = i – j+1,j=0,转到①
⑤若j>=n2,则匹配成功,返回i-j,否则匹配失败,返回-1
KMP算法:
(由于该算法很难用语言来表达,我还一度因为这个算法,而想放弃整个笔记系列o(╯□╰)o,我只能尽力将该算法叙述清楚~有什么不对的地方还请大家指正。)
首先我们需要来分析一下,为什么会出现KMP算法,主要原因是上面的BF算法效率很低,每一个字符都需要比较,即在最坏情况下,它的时间代价是O(n1*n2),这实在是太慢了……所以人们有了对更快算法的需求,于是,KMP算法应运而生了。KMP算法是由D.E.Knuth,J.H.Morris和V.R.Pratt共同设计的,于是简称为KMP算法。
分析BF算法的执行过程可知,造成BF算法速度慢的原因是回朔,即在某一趟匹配过程失败后,对于S串要回到本趟开始字符的下一个字符,T要回到第一个字符,其实这些回朔并不是必要的。
当某趟在S[i]和T[j]匹配失败后,指针i不回朔,模式T向右滑动至某个位置上,使得T[k]对准S[i]继续向右进行,如图所示:
要满足这样的关系,就必须要有以下关系成立:
T[1]T[2]T[3]……T[k-1] == T[j-k+1] T[j-k+2]T[j-k+3]……T[j-1]
如果以上关系成立,那么某趟在S[i]和T[j]匹配失败后,模式T中的前K-1个字符与模式中T[j] 字符前面的k-1个字符相同,模式T就能向右“滑动”使T[k]和S[i]对准,继续向右比较进行。
从上面的叙述可以看出,最重要的是对于每一个T[j],要找到合适K,使得上面的关系成立。
现在我们来看看应该如何来求这个K,我们定义一个辅助数组Next[n2],使得Next中的元素都对应于一个相应的k,即Next[i]=k(k就是当T[i]发生不匹配时,应该“滑动”到的位置)
对于Next[k]是一个递推地过程,算法流程图如下:
求next数组的算法如下:
//计算KMP模式匹配时的next数组
void ADTString::GetNext(char *t)
{
int length = strlen(t) - 1;
next[0] = -1;
//i:当前要求的next的元素的下标
//k:0~k-1和i-k~i-1相匹配
int i,k;
for(i = 0,k = -1; i < length; )
{
if(k == -1 || t[i] == t[k])
{
i++;
k++;
next[i] = k;
}
else
{
k = next[k];
}
}
}
利用next数组进行模式匹配:
int ADTString::KMPMatch(char *t)
{
int i,j;
//得到模式串的长度
int tn = strlen(t);
//计算next数组
GetNext(t);
//开始模式匹配,j==-1是一种特殊情况,表示需要从t的最开始匹配
//注意:我们的字符串是从0开始的,所以是j<tn和i<sn,而没有等号
for(i = 0,j = 0; i < sn && j < tn; )
{
if(j == -1 || s[i] == t[j])
{
++i;++j;
}
else
{
j = next[j];
}
count++;
}
//返回匹配的第一个字符的下标
if(j >= tn)
{
return i - tn;
}
else
{
return -1;
}
}