DataStructure_5.String

来源:互联网 发布:相片冲印软件 编辑:程序博客网 时间:2024/04/30 12:28

5.1

5.1.1串即字符串,由零个或多个字符组成的有限(有限指串的长度n是有限数值)序列,一般记为s="a1a2a3…an"(n≥0),注意引号不属于串的内容ai(1≤i≤n)可以是字母,数字或其他字符,i就是该字符在串中的位置。n称为串的长度。零个字符的串称为空串(null string),长度为零,可以直接用双引号表示"""",也可以用空集符号Ф表示,串的相邻字符之间具有前驱与后继的关系。

5.1.2

主串中任意连续字符组成的子序列称为该串的子串,相应的,包含子串的串称为主串,

子串在主串中的位置就是子串的第一个字符在主串中的序号。比如loverlovefriendend就是主串和子串的关系。

5.2串的比较

当两个串s="a1a2a3…an"t="b1b2b3…bm"不相等时,规定s<t在满足

n<mai=bi时,如s="hap",t="happy"时,有s<t,因为ts多两个字母

存在某个k≤min(m,n),使得ai=biak<bk,如s="happen",t="happy"时,两串前4个字母均相同,而两串第5个字母(k),字母eASCⅡ码101<字母yASCⅡ码121所以s<t

两个条件之一时成立

5.3串的存储结构

5.3.1串的顺序存储结构

一般使用定长数组来定义,串的实际长度可以保存在数组下标0的位置也可以保存在最后一个下标位置,也可以像c/c++那样在串后面接一个"\0"来表示串的终结,这时串的长度跑一个遍历就知道了。

但定长数组不可避免的会遇到一个问题,在字符串的操作中(比如两串的连接,新串的插入,字符串的替换),都有可能使得数组长度爆掉。所以可以使用一个叫做""的动态存储区(可通过C中的malloc()free()来管理)来创立动态数组,解决定长数组的弊端。

5.3.2串的链式存储结构

本身与线性表是相似的,但由于串结构的特殊性,一个结点可以考虑存放多个字符,当最后一个结点若是未被占满,可以用"#"或其他非串值字符补全。至于一个结点存放多少字符合适,就需要根据实际情况来选择。

注意:除了在连接串与串操作的时候方便一些之外,总体不如顺序存储灵活,性能也不如顺序存储结构好。

5.4模式匹配算法

5.4.1朴素的模式匹配算法

通俗来讲就是比如要匹配主串"pdf expert5"和子串"expert",朴素算法就是从主串的第一个字符开始与子串的第一个字符比较,不一样就移动到主串的第二个字符继续比较,直到第5个字符,匹配,那么再比较第6个字符与子串的第2个字符,依旧相同,继续继续,最终全匹配,比较成功。

/*假设主串s与子串t的长度存储在s[0]t[0]中,返回子串在主串中第pos个字符之后的位置(t非空,1≤pos≤StrLength(s)),若不存在,则返回0*/

int Index(String S,String T,int pos)

{

int i=pos;//i为主串s中当前位置的下标,若pos不为1,则从pos位置开始匹配

int j=1;//j用于子串t中当前位置的下标

while(i<=s[0]&&j<=t[0]){//i小于s长度且j小于t的长度

if(s[i]==[j]){//匹配则继续比较

++i;

++j;

}

else{//指针后退重新开始匹配

i=i-j+2;//i退回到上次匹配首位的下一位

j=1;//j退回到子串t的首位

}

}

if(j>t[0]) return i-t[0];

else return 0;

}

最好情况就是比如主串为happy子串为hap,一开始就匹配成功,时间复杂度为O(1),差一点的就是happy中找py,每次每次首字母就不匹配,t串循环不进行,那么时间复杂度为O(n+m)[n为主串长度,m为要匹配的子串长度。对于例子即为O(5+2=7)],于是平均情况即为(n+m/2)次查找,故平均时间复杂度为O(n+m),最差情况就是每次匹配都是前面都对的上就最后一个字母不匹配,比如ggggggggggggy中找gggggggy,时间复杂度是O((n-m+1)*m)

5.4.2 KMP模式匹配算法

说得通俗一点,就是朴素匹配方法效率太低,很多时候明明由于已经知道是不一样的而可以省略的比较操作还硬是要再比一次。KMP就解决了这个问题,已经知道是不同的了的,可以省略的比较步骤在这里实现省略。

KMP涉及到一个next数组问题,next数组中存储的是名为j的变量的变化情况,j表示的是要匹配的子串中各个字符的位置,next数组中存放的就是j所代表的子串的字符在各种情况下的代号

next[j]

0j=1

k,如果前后缀有一个字符相等,k=2(abca前缀字符a与后缀字符a相等);二个字符想等,k=3(abcab前缀字符ab与后缀ab想等)n个则为n+1

1,其他情况

next数组推导基本步骤:

以T="ababaaaba"为例:

j

123456789

模式串T

ababaaaba

next[j]

011234223

  1. j1时,next[1]=0
  2. j=2时,同上next[2]=1
  3. j=3时,同上next[3]=1
  4. j=4时,j1j-1的串是"aba"前缀字符a与后缀字符a相等,next[4]=2
  5. j=5时,j1j-1的串是"abab"前缀字符ab与后缀字符ab相等,next[5]=3
  6. j=6时,j1j-1的串是"ababa"前缀字符aba与后缀字符aba相等,next[6]=4
  7. j=7时,j1j-1的串是"ababaa"前缀字符ab与后缀字符aa不相等,只有"a"相等,故next[7]=2
  8. j=8时,j1j-1的串是"ababaaa"前缀字符ab与后缀字符aa不相等,只有"a"相等,故next[8]=2
  9. j=9时,j1j-1的串是"ababaaab"前缀字符ab与后缀字符ab相等,next[9]=3

KMP算法实现

//计算返回子串Tnext数组

void get_next(String T,int *next)

{

int i,j;

i=1;//初始化时i指向第一个

j=0;//初始化时设j初值为0

next[1]=0;//由算法,j=1next[j]=0

while(i<T[0]){//T[0]表示串T的长度,当i未到末尾时继续循环

if(j==0||T[i]==T[j]){//T[i]表示后缀的单个字符,T[j]表示前缀的单个字符

++i;

++j;

next[j]=j;

}

else j=next[j];//若字符不同,则j值回溯

}

}

//返回子串T在主串S中第pos个字符后的位置,若不存在,则函数返回值为0

//T非空,1≤pos≤StrLength(S)

int Index_KMP(String S,String T,int pos)

{

int i=pos;//i用于主串S中当前位置下标值,若pos不为1则从pos开始匹配

int j=1;//j用于子串T中当前位置下标值

int next[255];//定义一next数组

get_next(T,next);//对串T做分析,得到next数组

while(i<=S[0] && j<=T[0]){//i小于S的长度且j小于T的长度时,循环继续

if(j==0||S[i]==T[j]){两字母相等则继续,相对于朴素算法增加了j=0判断

++i;

++j;

}

else{//指针后退重新开始匹配

j=next[j];//j退回合适的位置,i值不变

}

}

if(j>T[0]) return i-T[0];

else return 0;

}

与朴素算法不同之处均高亮显示,关键是去掉了i值回溯部分,但KMP优势体现在子串与主串有许多"部分匹配"的情况

KMP算法仍有改进空间,如比较"gggggy"与母串"gggggapple"时,子串第二三四五位置的字符均与首位相同,所以可用首位next[1]的值去取代与它相等字符后续next[j]的值。

具体实现以及新next数组推导见书143页,等之后学到串这一章再继续消化。

0 0