【课上笔记】第四章 串

4.1串及其基本运算

4.1.1串的基本概念

​ 串是由零个或多个任意字符组成的字符序列。一般记作:s=“s1 s2 … sn”

​ 其中s是串名;在本书中,用双引号作为串的定界符,引号引起来的字符串列为串值,引号本身不属于串的内容;

​ ai(1<=i<=n)是一个任意字符,它称为串的元素,是构成串的基本单位,i是它在整个串中的序号。

​ n为串的长度,表示串中所包含的字符个数,当n=0时,称为空串,通常记作Ф。

·子串与主串:

​ 串中任意连续的字符组成的子序列称为该串的子串。

包含子串的串相应地称为主串。

·子串的位置:

​ 子串的第一个字符在主串中的序号称为子串的位置。

·串相等:

​ 称两个串是相等的,是指两个串的长度相等且对应字符都相等。

4.1.2 串的基本运算

⒈求串长 StrLength(s):操作结果是求出串s的长度。

⒉串赋值 StrAssign(s1,s2):s1是一个串变量,s2或者是一个串常量,或者是一个串变量(通常s2是一个串常量时称为串赋值,是一个串变量称为串拷贝),操作结果是将s2的串值赋值给s1, s1原来的值被覆盖掉。

⒊连接操作 StrConcat (s1,s2,s) 或 StrConcat (s1,s2):两个串的连接就是将一个串的串值紧接着放在另一个串的后面,连接成一个串。前者是产生新串s,s1和s2不改变; 后者是在s1的后面联接s2的串值,s1改变, s2不改变。

⒋求子串SubStr(s,i,len):串s存在并且1≤i≤StrLength(s),0≤len≤StrLength(s)-i+1。操作结果是求得从串s的第i个字符开始的长度为len的子串。len=0得到的是空串。

⒌串比较 StrCmp(s1,s2):操作结果是若s1==s2,操作返回值为0;若s1<s2,返回值<0;若s1>s2,返回值>0。

⒍子串定位 StrIndex(s,t) :s为主串,t为子串,操作结果是若t∈s,则操作返回t在s中首次出现的位置,否则返回值为0。

⒎串插入 StrInsert(s,i,t):串s,t存在,且1≤i≤StrLength(s)+1。操作结果是将串t插入到串s 的第i个字符位置上,s的串值发生改变。

⒏串删除StrDelete(s,i,len):串s存在,并且1≤i≤StrLength(s),0≤len≤StrLength(s)-i+1。操作结果是删除串s中从第i个字符开始的长度为len的子串,s的串值改变。

⒐串替换 StrRep(s,t,r):串s,t,r存在且t不为空,操作结果是用串r 替换串s中出现的所有与串t相等的不重叠的子串,s的串值改变。

​ 串的基本操作中前5个操作是最为基本的,它们不能用其他的操作来合成,因此通常将这5个基本操作称为最小操作集。

4.2串的定长顺序存储及基本运算

4.2.1串的定长存储

​ 串的定长顺序存储:用一组地址连续的存储单元存储串值中的字符序列,所谓定长是指按预定义的大小,为每一个串变量分配一个固定长度的存储区。

例如:

#define MAXSIZE 256

​ char s[MAXSIZE];

则串的最大长度不能超过256。

4.2.2定长顺序串的基本运算

如何标识串的实际长度?

1.类似顺序表,用一个指针来指向最后一个字符,这样表示的串描述如下:

​ typedef struct

{

char data[MAXSIZE]

int curlen;

}SeqString;

定义一个串变量:SeqString s;

这样可以直接得到串的长度:s.curlen+1

2.在串尾存储一个不会在串中出现的特殊字符作为串的终结符,以此表示串的结尾。例如C语言中就是用’\0’来表示串的结束。

这种存储方法不能直接得到串的长度,是用来判断字符是否是’\0’来确定串是否结束,从而求得串的长度。

3.设定长串存储空间:char s[MAXSIZE+1];

用s[0]存放串的实际长度,串值存放在s[1]~s[MAXSIZE],字符的序号和存储位置一致,应用更为方便。

下面的基本运算采用方法2,设串结束用'\0'来标识。        

​ 主要讨论定长串联接、求子串、串比较算法,顺序串的插入和删除等运算基本与顺序表相同,在此不在赘述。

1.串链接
int StrConcat1(char s1[],char s2[],char s[]){
    int i,j,len1,len2;
    i=0;
    len1=StrLength(s1);
    len2=StrLength(s2);
    if(len1+len2>MAXSIZE-1)
        return 0;//串存储空间不够,返回错误代码0
    j=0;
    while(s1[j]!='\0')
    {
        s[i]=s1[j];//将s1串值赋给s
        i++;
        j++;
    }
    j=0;
    while(s2[j]!='\0')
    {
        s[i]=s2[j];//将s2串值赋给s
        i++;
        j++;
    }
    return 1;
}
2.求子串
int StrSub(char *t,char *s,int i,int len){
    //用t返回串s中第i个字符开始的长度为len的子串1<=i<=串长
    int j;
    int slen;
    slen=StrLength(s);
    if(i<1||i<slen||len<0||len>slen-i+1){
        cout<<"参数不对"<<endl;
        return 0;//所给参数不符合要求,返回错误代码0
    }
    for(j=0;j<len;j++)
        t[j]=s[i+j-1];//将对应子串值赋给t
    t[j]='\0';//建立t串结束标记
    return 1;
        
}
3.串比较
int StrComp(char *s1,char *s2){
    int i;
    i=0;
    while(s1[i]==s2[i]&&s1[i]!='\0')//两串对应位置字符比较
        i++;
    return(s1[i]-s2[i]);//返回首个对应位置不同的字符的ASCII码差值
}

4.2.3模式匹配

​ 串的模式匹配即子串定位是一种重要的船运算。设s和t是给定的两个串,在主串s中查找子串t的过程称为模式匹配。如果在s中找到等于t的子串,则匹配成功,函数返回t在s中的首次出现的存储位置(序号),否则匹配失败,返回0。t也成为模式。

​ 为了运算方便,设字符串采用定长存储,且用第三种方式表示串长,即串的长度存放在0号单元,串值从1号单元存放,这样字符序号与存储位置一致。

  1. 朴素模式匹配算法(Brute-Force算法)

    求子串位置的定位函数Index( S, T, pos)

    ·模式匹配:子串的定位操作通常称作串的模式匹配。

    ·目标串:主串S。

    ·模式串:子串T。

    ·匹配成功:若存在T的每个字符依次和S中的一个连续字符序列相等,则称匹配成功。返回T中第一个字符在S中的位置。

    ·匹配不成功:返回0。

​ Brute-Force简称为BF算法,亦称简单匹配算法,其基本思路是:

从目标串s=“s1s2…sn"的第一个字符开始和模式串t=“t1t2…tm"中的第一个字符比较,若相等,则继续逐个比较后续字符;否则从目标串s的第二个字符开始重新与模式串t的第一个字符进行比较。依次类推,若从模式串s的第i个字符开始,每个字符依次和目标串t中的对应字符相等,则匹配成功,该算法返回i;否则,匹配失败,函数返回0。

​ 算法思想如下:

①首先将s1与t1进行比较,若不同,就将s2与t1进行比较,…,直到s的某一个字符si和t1相同,再将它们之后的字符进行比较,若也相同,则如此继续往下比较,当s的某一个字符si与t的字符tj不同时,则s返回到本趟开始字符的下一个字符,即i-j+2,t返回到1,继续开始下一趟的比较;

②重复上述过程。若t中的字符全部比完,则说明本趟匹配成功,本趟的起始位置是i-j+1或i-t[0],否则,匹配失败。

int Stringdex_BF(char *s,char *t){//从串s的第一个字符开始找首次与串t相等的子串
     int i,j;
    i=1;
    j=1;
    while(i<=s[0]&&j<=t[0])//都没有遇到结束符
        if(s[i]==t[j])//对应位置字符比较
        {
            i++;
            j++;
        }//相等,继续
       else{
           i=i-j+2;
           j=1;
       }//不等,回溯
    if(j>t[0])
        return(i-t[0]);//匹配成功,返回子串首字符存储位置
    else
        return 0;//匹配失败
}

​ 上述算法中匹配是从s串的第一个字符开始的,有时算法要求从指定位置开始,这时算法的参数表中要加一个位置参数pos:StrIndex(char *s,int pos,char *t),比较的初始位置定位在pos处。上述算法可以看作是pos=1时的特殊情况。

字串定位算法

int Index(char *s,int pos,char *t)
{
    i=pos;j=1;
    while(i<=s[0]&&j<=t[0])
    {
        if(s[i]==t[i])
        {
            ++i;
            ++j;
        }
        else
        {
            i=i-j+2;
            j=1;
        }
        if(j>t[0]) return i=t[0];
        else return 0;
    }
}

时间复杂度分析:设串长度为n,串t长度为m。匹配成功的情况下,参考两种极端情况:

​ ①在最好情况下,每趟不成功的匹配都发生在第一对字符比较时

​ 设匹配发生在si处,则字符比较次数在前面i-1趟匹配中共比较了i-1次,第i趟成功的匹配共比较了m次,所以共比较了i-1+m次,所有匹配成功的可能共有n-m+1种,设从si开始与t串匹配成功的概率为pi,等概率情况下pi=1/(n-m+1),因此最好情况下平均比较的次数是:

时间复杂度是O(n+m)

​ ②在最坏情况下,每趟不成功的匹配都发生在t的最后一对字符。

​ 设匹配成功发生在si处,则在前面i-1趟匹配中共比较了(i-1)*m次,第i趟成功的匹配共比较了m次,所以共比较了i *m次,因此最坏情况下平均比较的次数是:
请添加图片描述

时间复杂度是O(n*m)

KMP算法

​ KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的,简称KMP算法。该算法较BF算法有较大改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

​ 改进思想:

​ 每趟匹配过程中出现字符比较不等时,不回溯主指针i,利用已得到的“部分匹配”结果将模式(即子串)向右滑动尽可能远的一段距离,继续进行比较。

​ 定义next[j]函数,表明当模式中第j个字符与主串中相应字符“失配”时,在模式串中需要重新和主串中该字符进行比较的字符的位置,即指针j的回溯位置。

请添加图片描述

Next[j]即回溯位置k仅仅与模式串有关!

改进的模式匹配算法

int Index_KMP(char *s,int pos,char *t)
{
    i=pos;
    j=1;
    while(i<s[0]&&j<t[0])
    {
        if(j==0||s[i]==t[j])//是第一个位置的模式串及目标串字符不匹配时,j=next[1]=0后的处理,i=2,j=1,即下一次从模目标串的第2个字符开始匹配
        {
            i++;
            j++;
        }
        else j=next[j];//i不变,j回溯  
    }
    if(j>t[0]) return i-t[0];
    else return 0;
}

KMP算法中的next函数值求解算法

void get_next(char *t,int next[])
{
    j=1;next[1]=0;k=0;
    while(i<t[0])
    {
        if(j==0||t[j]==t[k])
        {
            j++;k++;
            next[j]=k;
        }
        else
            k=next[k];//t[j]不等于t[K]
    }
}

KMP算法的时间复杂度

​ 设主串s的长度为n,模式串t长度为m,在KMP算法中求next数组的时间复杂度为O(m),在后面的匹配中因主串s的下标不减即不回溯,比较次数可记为n,所以KMP算法总的时间复杂度为O(n+m)。

4.3串的堆存储结构

串名的存储映像

​ 串名的存储映象是串名-串值内存分配对照表,也称为索引表。表的形式有多种表示,常见的串名-串值存储映象索引表有如下几种: 带串长度的索引表 末尾指针的索引表 带特征位的索引表

​ 1.带串长度的索引表

索引项的结点类型为:

typedef struct

{ char name[MAXNAME]; /串名/

​ int length; /串长/

​ char *stradr; /起始地址/

} LNode;

​ 2.末尾指针的索引表

索引项的结点类型为:

typedef struct

{ char name[MAXNAME]; /串名/

​ char *stradr,*enadr;  /起始地址,末尾地址/ } ENode;

​ 3.带特征的索引表

当一个串的存储空间不超过一个指针的存储空间时,可以直接将该串存在索引项的指针域,这样既节约了存储空间,又提高查找速度,但这时要加一个特征位tag以指出指针域存放的是指针还是串。

索引项的结点类型为:

typedef struct

{ char name[MAXNAME];

​ int tag; /特征位/

​ union  /起始地址或串值/

​ { char *stradr;

​ char value[4];

​ }uval;

} TNode;

堆存储结构

​ 在应用程序中,参与运算的串变量之间的长度相差较大,并且操作中串值的长度变化也较大,因此为串变量预分配固定大小的空间不尽合理。 堆存储结构的基本思想:在内存中开辟能存储足够多的串、地址连续的存储空间作为应用程序中所有串的可利用存储空间,称为堆空间,如设store[SMAX+1]; 根据每个串的长度,动态的为每个串在堆空间里申请相应大小的存储区域,这个串顺序存储在所申请的存储区域中,当操作过程中若原空间不够了,可以根据串的实际长度重新申请,拷贝原串值后再释放原空间。

​ 堆结构上的串运算仍然基于字符序列的复制进行,基本思想是:当需要产生一个新串时,要判断堆空间中是否还有存储空间,若有,则从free指针开始划出相应大小的区域为该串的存储区,然后根据运算求出串值,最后建立该串存储映象索引信息,并修改free指针。 设堆空间为: char store[SMAX+1]; 自由区指针:int free;

​ 串的存储映象类型如下:

​ typedef struct

​ { int length; /串长/

​ int stradr; /起始地址/

​ } HString;

基于堆结构的基本运算

1.串常量赋值
int StrAssign(HString *s1,char *s2){
    //将一个字符型数组s2中的字符串送入堆store中,free是自由区的指针
    int i,len;
    i=0;
    len=StrLength(s2);
    if(len<0||free+len-1>SMAX)
        return 0;//堆中自由区空间不够,返回错误代码0
    else
    {
        for(i=0;i<len;i++)
            store[free+i]=s2[i];//将s2串值赋值给堆
        s1->stradr=free;//建立s1串存储映像索引信息
        s1->length=len;
        free=free+len;
    }
}
2.赋值一个串
void StrCopy(Hstring *s1,Hstring s2)
/*该运算将堆store 中的一个串s2 复制到一个新串s1 中*/
{ 
    int i;
	if (free+s2.lengt-1>SMAX) 
        return error ;
	else 
     { for(i=0; i<s2.length;i++)
		store[free+i]=store[s2.atradr+i];
	   s1->length=s2.length;
       s1->stradr=free;
       free=free+s2.length;
     }
}
3.求子串
void StrSub(Hstring *t, Hstring s,int i,int len)
/*该运算将串s 中第i 个字符开始的长度为len 的子串送到一个新串t 中*/
{ 
    int i;
	if (i<0 || len<0 || len>s.len-i+1) 			return error ;
	else 
    { 
        t->length=len;
		t->stradr=s.stradr+i-1;
	}
}
4.串连接
void Concat(s1,s2,s)
HString s1,s2;
HString *s;
{ HString t;
StrCopy (s,s1);
StrCopy (&t,s2);
s->length=s1.length+s2.length;
}

以上堆空间和算法是由算法编写者自己设计和编写来实现的,在这里,重点介这种存储的处理思想,很多问题及细节尚未涉及,比如,废弃串的回归、自由区的管理问题等等。在常用的高级语言及开发环境中,大多系统本身都提供了串的类型及大量的库函数,用户可直接使用,这样会使算法的设计和调试更方便容易,可靠性更高。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值