第四章 串、数组和广义表

波兰表达式

在这里插入图片描述

串的定义

字符串(string) 是由零个或多个字符组成的有限序列,一般记为 s=“a1 a2 … an” (n≥0)
其中,s是串的名,用双引号标识的字符序列是串的值;可以是字母、数字或其他字符;串中字符的数目n称为串的长度。零个字符的串称为空串(null string),其长度为0。
串中任意个连续的字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。通常称字符在序列中的序号为该字符在串中的位置。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。

当且仅当两个串的值相等,称这两个串是相等的。也就是说,只有当两个串的长度相等,并且各个对应位置的字符都相等时两个串才相等。
在各种应用中,空格常常是串的字符集合中的一个元素,因而可以出现在其他字符中间。由
一个或多个空格组成的串“ ”称为空格串(blank string,请注意:此处不是空串),其长度为串
中空格字符的个数。为清楚起见,以后我们用符号“Ø”来表示“空串”。

串的类型定义、存储结构及运算

串的抽象类型定义

串的逻辑结构和线性表极为相似,区别仅在于串的数据对象约束为字符集。
串的抽象数据类型的定义如下:
在这里插入图片描述
在这里插入图片描述

串的存储结构

  1. 串的顺序存储
//- - - - - 串的定长顺序存储结构- - - - - 
#define MAXLEN 255 //串的最大长度
typedef struct{
 char ch[MAXLEN+1]; //存储串的一维数组
 int length; //串的当前长度 
}SString;

在C语言中,存在一个称之为“堆”(Heap)的自由存储区,可以为每个新产生的串动态分配一块实际串长所需的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基址,同时为了以后处理方便,约定串长也作为存储结构的一部分。这种字符串的存储方式也称为串的堆式顺序存储结构,定义如下:

//- - - - - 串的堆式顺序存储结构- - - - - 
typedef struct{
 char *ch; //若是非空串,则按串长分配存储区,否则ch为NULL
 int length; //串的当前长度 
}HString;
  1. 串的链式存储
    在这里插入图片描述

图4.3(a)所示为节点大小为4(每个节点存放4个字符)的链表,
图4.3(b)所示为节点大小为1的链表。
当节点大小大于1时,由于串长不一定是节点大小的整倍数,则链表中的最后一个节点不一定全被串值占满,此时通常补上“#”或其他的非串值字符(通常“#”不属于串的字符集,是一个特殊的符号)。

//- - - - - 串的链式存储结构- - - - - 
#define CHUNKSIZE 80 //可由用户定义的块大小 
typedef struct Chunk{
 char ch[CHUNKSIZE];
 struct Chunk *next;
}Chunk;
typedef struct{
 Chunk *head,*tail; //串的头和尾指针 
 int length; //串的当前长度 
}LString;

串的模式匹配算法

子串的定位运算通常称为串的模式匹配串匹配。

设有两个字符串S和T,设S为主串,也称正文串;设T为子串,也称为模式。在主串S中查找与模式T相匹配的子串,如果匹配成功,确定相匹配的子串中的第一个字符在主串S中出现的位置。

著名的模式匹配算法有BF算法KMP算法

  1. BF 算法
    最简单直观的模式匹配算法是BF(Brute-Force)算法。
int Index_BF(SString S,SString T,int pos)
{//返回模式T在主串S中第pos个字符开始第一次出现的位置。若不存在,则返回0 
 //其中,T非空,1≤pos≤S.length
 i=pos; j=1; //初始化
 while(i≤S.length && j≤T.length) //两个串均未比较到串尾
 { 
   if(S.ch[i]==T.ch[j]){++i;++j;} //继续比较后继字符
   else{i=i-j+2;j=1;} //指针后退,重新开始匹配
 }
 if(j>T.length) return i-T.length; //匹配成功
 else return 0; //匹配失败
}

在这里插入图片描述
【算法分析】
在这里插入图片描述
在这里插入图片描述

  • 最好情况下的平均时间复杂度是O(n + m)。
  • 最坏情况下的平均时间复杂度是O(n × m)。
  1. KMP 算法

这种改进算法是由克努特(Knuth)、莫里斯(Morris)和普拉特(Pratt)共同设计实现的,因此简称KMP算法。此算法可以在O(n + m)的时间数量级上完成串的模式匹配操作。其改进在于:每当一趟匹配过程中出现字符不等时,无须回溯主串的指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。

在这里插入图片描述
在这里插入图片描述
【KMP算法】

int Index_KMP(SString S,SString T,int pos)
{//利用模式串T的next函数求T在主串S中第pos个字符之后的位置 
 //其中,T非空,1≤pos≤S.length
 i=pos;j=1;
 while(i<=S.length && j<=T.length) //两个串均未比较到串尾
 {
   if(j==0‖S.ch[i]==T.ch[j]){++i;++j;} //继续比较后续字符 
   else j=next[j]; //模式串向右移动 
 }
 if(j>T.length) return i-T.length; //匹配成功 
 else return 0; //匹配失败
}

【计算next函数值】

void get_next(SString T,int next[])
{//求模式串T的next函数值并将其存入数组next
 i=1;next[1]=0;j=0 ; 
 while(i<T.length)
 {
   if(j==0‖T.ch[i]==T.ch[j]){++i;++j;next[i]=j;}
   else j=next[j];
  }
}

【计算next函数修正值】

void get_nextval(SString T,int nextval[])
{//求模式串T的next函数修正值并将之存入数组nextval 
 i=1;nextval[1=0;j=0;
 while(i<T.length)
 {
   if(j==0‖T.ch[i]==T.ch[j])
   {
     ++i;++j;
     if(T.ch[i]!=T.ch[j]) nextval[i]=j;
     else nextval[i]=nextval[j];
   }
   else j=nextval[j];
 }
}

KMP算法中next数组和nextval数组是如何快速求解的

在这里插入图片描述

求出next数组和nextval数组后如何跟主串进行匹配

在这里插入图片描述

数组

数组的类型定义

数组是由类型相同的数据元素构成的有序集合,每个元素称为数组元素,每个元素受n(n≥1)个线性关系的约束,每个元素在n个线性关系中的序号i1,i2,…,in称为该元素的下标,可以通过下标访问该数据元素。因为数组中每个元素处于n(n≥1)个关系中,故称该数组为n维数组。数组可以看成线性表的推广,其特点是结构中的元素本身可以是具有某种结构的数据,但属于同一数据类型。

例如,一维数组可以看成一个线性表,二维数组可以看成数据元素是线性表的线性表。图4.10(a)所示的二维数组可以看成一个线性表:

在这里插入图片描述

抽象数据类型数组可定义为:
在这里插入图片描述

数组的顺序存储

在这里插入图片描述
在这里插入图片描述
假设每个数据元素占L个存储单元,则二维数组A[0… m−1, 0… n−1](下标从0开始,共有m行n列)中任一元素aij的存储位置可由下式确定:
LOC(i, j) = LOC(0, 0) + (n×i + j)L

其中,LOC(i, j)是aij的存储位置;LOC(0, 0) 是a00的存储位置,即二维数组A的起始存储位置,也称
为基地址或基址。

求一维、二维数组的存储位置

在这里插入图片描述
在这里插入图片描述

特殊矩阵的压缩存储

假若值相同的元素或者零元素在矩阵中的分布有一定规律,则称此类矩阵为特殊矩阵。 特殊矩阵主要包括对称矩阵、三角矩阵和对角矩阵等,下面我们重点讨论这3种特殊矩阵的压缩存储。

  1. 对称矩阵
    在这里插入图片描述

  2. 三角矩阵
    在这里插入图片描述

  3. 对角矩阵
    在这里插入图片描述

广义表

广义表的定义

顾名思义,广义表是线性表的推广,也称为列表。
在这里插入图片描述

从上述定义和例子可推出广义表的如下3个重要结论。
在这里插入图片描述
表头、表尾均为空表。

广义表的存储结构

由于广义表中的数据元素可以有不同的结构(或是原子,或是列表),因此难以用顺序存储结构表示,通常采用链式存储结构。常用的链式存储结构有两种,头尾链表的存储结构和扩展线性链表的存储结构。

  1. 头尾链表的存储结构
    由于广义表中的数据元素可能为原子或广义表,因此需要两种结构的节点:一种是表节点,用以表示广义表;一种是原子节点,用以表示原子。
    在这里插入图片描述
//- - - - -广义表的头尾链表存储表示- - - - - 
typedef enum{ATOM,LIST} ElemTag; //ATOM==0 :原子 ;LIST==1 :子表
typedef struct GLNode
{ 
 ElemTag tag; //公共部分,用于区分原子节点和表节点
 union //原子节点和表节点的联合部分
 {
   AtomType atom; //atom是原子节点的值域,AtomType由用户定义
   struct{struct GLNode*hp,*tp;}ptr; 
                //ptr是表节点的指针域,ptr.hp 和ptr.tp 分别指向
表头和表尾
 }; 
}*GList; //广义表类型

在这里插入图片描述
(1)除空表的表头指针为空外,对任何非空广义表,其表头指针均指向一个表节点,且该节点中的hp域指向广义表表头(或为原子节点,或为表节点),tp域指向广义表表尾(除非表尾为空,则指针为空,否则必为表节点)。

(2)容易分清列表中原子和子表所在层次。如在广义表D中,原子a和e在同一层次上,而b、c和d在同一层次且比a和e低一层,B和C是同一层的子表。

(3)最高层的表节点个数即广义表的长度。

以上3个特点在某种程度上可给广义表的操作带来方便。

  1. 扩展线性链表的存储结构
    在这里插入图片描述

小结

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值