数据结构08——串的定义及朴素的模式匹配算法

160 阅读1分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

1 串(String)的定义

是由零个或多个字符组成的有限序列,又叫字符串。

一般记为s=a1a2an(n0)"s=”a_1a_2···a_n (n\geq0)",其中,s是串的名称,用双引号括起来的字符序列是串的值,aia_i可以是字母、数字或其他字符,ii就是该字符在串中的位置。

串中的字符数目nn称为串的长度,零个字符的串称为空串(null string)。所谓序列说明串的相邻字符之间具有前驱和后继的关系。

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

2 串的比较

串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。

给定两个串:s=a1a2an(n0)"s=”a_1a_2···a_n (n\geq0)"t=b1b2bm(m0)"t=”b_1b_2···b_m (m\geq0)",当且仅当n=mn=m时,且a1=b1a_1=b_1a2=b2a_2=b_2,·····,an=bma_n=b_m,我们认为s=ts=t

那么对于两个串不相等时,如何判定它们的大小呢?

给定两个串:s=a1a2an(n0)"s=”a_1a_2···a_n (n\geq0)"t=b1b2bm(m0)"t=”b_1b_2···b_m (m\geq0)",当满足以下条件之一时,sts\leq t

  1. n<mn<m,且ai=bi(i=1,2,,n)a_i=b_i(i=1,2,···,n)

    例如当s=“hap”,t=“happy”,就有s<t。因为t比s多出了两个字母。

  2. 存在某个kmin(m,n)k\leq min(m,n),且ai=bi(i=1,2,,k1),ak<bka_i=b_i(i=1,2,···,k-1),a_k<b_k

    例如当s=“happen”,t=“happy”,因为两串的前4个字母均相同,而两串第5个字母(k值),字母e的ASCII码是101,而字母y的ASCII码是121,显然e<y,所以s<t。

3 串的抽象数据类型

对于串的基本操作与线性表是有很大差别的。线性表更关注的是单个元素的操作,比如查找一个元素、插入或删除一个元素,但串中更多的是查找子串位置、得到指定位置子串、替换子串等操作。

串的伪代码定义如下:

ADT 串(string)
Data
    串中元素仅由一个字符组成,相邻元素具有前驱和后继关系。
Operation
    StrAssign(T,*chars):生成一个其值等于字符串常量chars的串T。
    StrCopy(T,S):串S存在,由串S复制到串T。
    ClearString(S):串S存在,将串清空。
    StringEmpty(S):若串S为空,返回true,否则返回falseStrLength(S):返回串S的元素个数,即串的长度。
    StrCompare(S,T):若S>T,返回值大于0,若S=T,返回值等于0,若S<T,返回值小于0。
    Concat(T,S1,S2):用T返回S1和S2连接而成的新串。
    SubString(Sub,S,pos,len):S存在,1≤pos≤StrLength(S),且			                                01en≤StrLength(S)-pos+1,用Sub返回串S的第                              pos个字符起长度为1en的子串。
    Index(S,T,pos):串S和T存在,T是非空串,1≤pos≤StrLength(S)。若主串S中存在和					  串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位				   置,否则返回0。
    Replace(S,T,V):串S,T,V存在,T是非空串。用V替代主串中S中出现的所有与T相等的不					 重叠的子串。
    StrInsert(S,pos,T):串S和T存在,1≤pos≤StrLength(S)+1。在串S的第pos个字符						 之前插入串T。
    StrDelete(S,pos,len):串S存在,1≤pos≤StrLength(S)+1。从串S中删除第pos个字						    符起长度为len的子串。
endADT

Index实现算法:

/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 其中,T非空,1≤pos≤StrLength(S)。 */
int Index(String s, String T, int pos){
    int n,m,i;
    String sub;
    if(pos>0){
        n = StrLength(S);  // 得到主串S的长度
        m = StrLength(T);  // 得到子串T的长度
        i = pos;
        while(i<=n-m+1){
            SubString(sub,S,i,m);  // 取子串第i个位置开始长度与T相等的字串给sub
            if(StrCompare(sub,T) != 0){  // 如果两串不相等
                ++i; 
            }else{  // 如果两串相等
                return i;  // 返回i值
            }
        }
    }
    return 0;  // 若无子串与T相等,返回0
}

StrLength实现算法:

/* 返回串的元素个数 */
int StrLength(String S)
{ 
	return S[0];
}

SubString实现算法:

/* 用Sub返回串S的第pos个字符起长度为len的子串。 */
Status SubString(String Sub,String S,int pos,int len)
{
	int i;
	if(pos<1||pos>S[0]||len<0||len>S[0]-pos+1)
		return ERROR;
	for(i=1;i<=len;i++)
		Sub[i]=S[pos+i-1];
	Sub[0]=len;
	return OK;
}

Strcompare实现算法:

/*  初始条件: 串S和T存在 */
/*  操作结果: 若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0 */
int StrCompare(String S,String T)
{ 
	int i;
	for(i=1;i<=S[0]&&i<=T[0];++i)
		if(S[i]!=T[i])
			return S[i]-T[i];
	return S[0]-T[0];
}

4 朴素的模式匹配算法

子串的定位操作通常称做串的模式匹配,最重要的就是去找一个单词在一篇中的定位问题。

假设要从主串S=“goodgoogle”中,找到T=“google”这个子串的位置,下面是进行的步骤。

  1. 主串S第—位开始, S与T前三个字母都匹配成功,但S第四个字母是d,而T的是g。第一位匹配失败。

    image-20220906223244231.png

  2. 主串S第二位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败。

    image-20220906223254536.png

  3. 主串S第三位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败。

    image-20220906223303882.png

  4. 主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败。

    image-20220906223312872.png

  5. 主串S第五位开始,S与T,6个字母全匹配,匹配成功。

    image-20220906223323194.png

简单的说,就是对主串的每个字符作为子串开头,与要匹配的字符串进行匹配,对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历为止。

模式匹配算法Index:

/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 其中,T非空,1≤pos≤StrLength(S)。 */
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] == T[j]){ 	/* 两字母相等则继续 */
            ++i;
            ++j;
        }else{ 				/* 指针后退重新开始匹配 */
            i = i-j+2;		/* i退回到上次匹配首位的下一位 */
            j = 1; 			/* j退回到子串T的首位 */
        }
    }
    if (j > T[0]){
        return i-T[0];
    }else{
        return 0;
    }
}

时间复杂度:O((nm+1)m)O((n-m+1)*m)