此篇为本人自己学习过程中的笔记,有许多不足之处,仅供参考。
目录
1.串类型的定义
串是字符串的简称,形象地说就是,把一列字符连成一串作为一个处理对象。 之前学习栈和队列都是线性结构,可以表示为,栈和队列是操作受限的线性表,而串是内容受限的线性表,数据元素只能是字符。在汇编程序和编译程序里面,源程序和目标程序都是字符串数据,在事务处理系统、文字编辑系统、事务问答系统、自然语言翻译系统以及音乐分析等一系列系统中,都是以字符串数据作为处理对象的。
把由零个或多个任意字符组成的有限数列叫串,如:
s为串名,等号后面双引号里面的字符序列是串的值,n是串的长度,n=0时为空串。空串用表示。
子串:串中任意个连续字符组成的子序列(含空串)称为该串的子串。
真字串:指不包含自身的所有字串。
主串:包含子串的串称为主串。
“abcde” 的字串有“ ” ,“a”,“ab”,“abc”,“abcd”,“abcde”。
字符位置:字符在序列中的序号为字符在串中的位置。
子串位置:子串中第一个字符在主串中的位置。
空格串:由一个或多个空格组成的串,与空串不同。
举例:
a="ZHONG",b="GUO",c="ZHONGGUO",d="ZHONG GUO"
它们的长度分别是5,3,8,9 c的子串是a,b d的子串是a,b
a在c中的位置1 b在c中的位置6 a在d中的位置6 b在d中的位置7
串相等:当且仅当两个串长度相等,且各个位置上对应的字符都相同时,这两个串才是相等的。
2.使用String定义串的抽象数据类型
ADT String
{
数据对象:
数据关系:
基本操作:串赋值,串比较,求串长,串连结,求子串,串拷贝,串判空,清空串,子串的位置
串替换,子串插入,子串删除,串销毁。
}ADT String
串的存储结构,串中的逻辑关系同线性表相同,所以可以采用与线性表相同的储存结构。
串的顺序存储结构,用数组实现。
#define MAXSIZE 100
class String
{
char ch[MAXSIZE + 1]; // 储存串的一维数组
int length; //串的当前长度
};
串的链式存储结构:优点操作方便,缺点,存储密度低。存放一个字符占用1个字节,地址需要4个字节。可以将多个字符放在一个节点中,克服缺点。称作块链结构。这实际上是一种顺序储存,链式储存相结合的结构,这种储存结构会增加基本操作的复杂度。例如,对改变串长的操作,可能涉及结点的增加与删除问题。
#define CHUNKSIZE 80 //块的大小由用户自定义
class Node
{
char ch[CHUNKSIZE];
Node* next;
};
class Chunk //字符串的块链结构
{
Chunk* head, * tail; //串的头指针与尾指针
int curlen; //串的长度
};
3. 串的模式匹配算法
3.1 算法目的
确定主串中所含子串(模式串)第一次出现的位置(定位)
3.2 算法应用
搜索引擎、拼写检查、语言翻译、数据压缩
3.3 算法种类
3.3.1 BF算法
Brute-Force, 又称古典的,经典的,朴素的,穷举的,简单匹配算法,采用穷举法的思路。
BF算法设计思想,BF从主串S中下标为i的字符与模式串P的第一个字符a开始逐个比较,遇到不相等时,即达到失配点,该趟匹配失败,S回到原来的i+1的位置,P回到第1个字符位置,继续下一趟匹配。直到匹配成功,返回P的第1个字符在S中的位置结束;或S中不存在P,匹配失败返回0结束。图为简单模式匹配算法的示意图,其中i和j分别为主串S和模式串P的下标。最后一趟匹配失败后,无须进行下一次匹配,因为主串中的字符数已经小于子串的串长。
图3.3.1 串的简单匹配算法
int String::Index_BF(String s, String p,int n)
{
int i = n, j = 1;
while (i <= s.length && j <= p.length)
{
if (s.ch[i] == p.ch[j])
{
i++;
j++;//主串和子串依次匹配下一个字符
}
else // 主串子串指针回溯
{
//j-1是移动了多少,i-(j-1)指回到之前的位置,再+1则向后又移一位
i = i - (j - 1) + 1;
j = 1;
}
if (j >= p.length)
{
return i - p.length;// 返回匹配成功第一个字符串的下标
}
else
{
return 0; //模式匹配不成功
}
}
主串长为n,子串长为m,可能匹配成功的位置为1~n-m+1。
最好的情况下,第i个位置匹配成功,比较了i-1+m次,平均比较次数
算法的时间复杂度为。
最坏的情况下,第i个位置匹配成功,比较了i×m次,平均比较次数
算法的时间复杂度为。
3.3.2 KMP算法
是由D.E.Knuth、J.H.Morris、V.R.Pratt共同提出的算法。该算法较BF算法由较大改进,主要是消除了主串指针的回溯,从而提高了算法的效率。提速到。
在3.3.1 的a图中,我们可以看到当第一趟匹配达到失配点时,只是子串中第五个字符匹配失败,前四个都已经匹配成功,所以i和j回溯到0,就显得毫无意义,跳过第2,3次的匹配,直接进行第四趟,在第一趟匹配结束时i=4,失配点前的字符是相等的,S[3]=P[0],所以j不用重新回溯到0,回溯到0只是把两个相等的字符比较,j只要回溯到1,继续比较后面的字符,就可以得到匹配结果。这样i只进不退,就消除了主串的回溯。
图3.3.2.1 回溯示意图
实现KMP算法的关键为:j要回溯到哪一个位置,设j要向前回溯k个位置,k是失配时,j需要向前回溯的最少位置。下一趟比较从S[i]与P[k]开始。为此,定义next[j]函数(失败函数),表明当模式中第j个字符与主串中相应字符失配时,在模式中重新和主串中该字符进行比较的字符位置。
图3.3.2.2 next函数
图3.3.2.3 函数举例
模式P="abcaabbcabcaabda"的函数如图所示,当j=1时,next[j]为0,当j=2时,前面只有一个字符,没有前缀,也没有后缀,为其他情况,next[j]=1,当j=3时,既没有相等的前缀后缀,j也不为1,仍然属于其他情况,next[j]=1;接下来看j=14时,相同的前缀、后缀子串有"a","abcaa",根据函数定义,取最大值5,此时k-1=5,k值为6。
void GetNext(String p, int & next[])
{
int k = 1,j = 0 ;
next[1] = 0;
while (k < p.length)
{
//当k=0或ch[i]与ch[j]相等时,i,j各扩展一位,将求得的j值存放在next[i]中
if (k==0||p.ch[k]==p.ch[j])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[j]; //k回溯到next[j],重新比较
}
}
}
3.3.3 next函数的改进
1.第一位的nextval值必定为0,第二位如果于第一位相同则为0,如果不同则为1。
2.第三位的next值为1,那么将第三位和第一位进行比较,均为a,相同,则第三位的nextval值为第一位的next值,为0。
3.第四位的next值为2,那么将第四位和第二位进行比较,不同,则第四位的nextval值为其next值,为2。
4.第五位的next值为2,那么将第五位和第二位进行比较,相同,第二位的next值为1,则继续将第二位与第一位进行比较,不同,则第五位的nextval值为第二位的next值,为1。
5.第六位的next值为3,那么将第六位和第三位进行比较,不同,则第六位的nextval值为其next值,为3。
6.第七位的next值为1,那么将第七位和第一位进行比较,相同,则第七位的nextval值为0。
7.第八位的next值为2,那么将第八位和第二位进行比较,不同,则第八位的nextval值为其next值,为2。
void GetNextUp(String p, int & next[])
{
int k = -1,j = 0 ;
next[0] = -1;
while (k < p.length)
{
//当k=-1或ch[i]与ch[j]相等时,i,j各扩展一位,将求得的j值存放在next[i]中
if (k == -1 || p.ch[k] == p.ch[j])
{
++k;
++j;
if (p.ch[k] == p.ch[j])
{
next[j] = next[k];
}
else
{
k = next[j];
}
}
else
{
k = next[k];
}
}
}