串、数组和广义表

  • 1、串
    • ①.串的定义
    • ②.串的顺序存储
    • ③.串的链式存储
    • ④.串的模式匹配算法
  • 2、数组
    • ①.数组的定义
    • ②.数组的顺序存储
    • ③.特殊矩阵的压缩存储
  • 3、广义表
    • ①.广义表的定义
    • ②.广义表的存储结构
  • 4、总结
  • 5、例题与应用

1、串

①.串的定义

串(String)----零个或多个字符组成的有限序列,是一种特殊的线性表,其数据元素为一个字符,即内容受限的线性表。

子串:

串中任意个连续的字符组成的子序列

主串:

包含子串的串

字符位置:

字符在序列中的序号

子串位置:

以子串的第一个字符在主串中的位置来表示

空格串:

一个或多个空格组成的串

两个串是相等的,当且仅当这两个串的值相等。也就是说,只有当两个串的长度相等,并且各个对应位置的字符都相等时才相等。例如,上例中的串a、b、c和d彼此都不相等。

串的抽象数据类型的定义:


与线性表类似,串也有两种基本存储结构:顺序存储和链式存储。但考虑到存储效率和算法的方便性,串多采用顺序存储结构。

②.串的顺序存储

类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用定长数组如下描述:

typedef struct
{char ch[MAXLEN + 1]; // MAXLEN为串的最大长度//存储串的一维数组int length; //串长度
} SString;

而多数情况下,串的操作是以串的整体形式参与的,串变量之间的长度相差较大,在操作中串值长度的变化也较大,这样为串变量设定固定大小的空间不尽合理。因此最好是根据实际需要,在程序执行过程中动态地分配和释放字符数组空间。在C语言中,存在一个称之为“堆”(Heap)的自由存储区,可以为每个新产生的串动态分配一块实际串长所需的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基址,同时为了以后处理方便,约定串长也作为存储结构的一部分。

typedef struct
{char *ch;   //若串非空,则按串长分配存储区,否则ch为NULLint length; //串长度
} HString;

③.串的链式存储

顺序串的插人和删除操作不方便,需要移动大量的字符。因此,可采用单链表方式存储串。

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

串值的链式存储结构对某些串操作,如联接操作等,有一定方便之处,但总地说来,不如顺序存储结构灵活,它占用存储量大且操作复杂。

④.串的模式匹配算法

子串的定位运算通常称为串的模式匹配或串匹配,主串称为正文串,子串称为模式串,该算法目的是确定主串中缩含子串第一次出现的位置(定位)。此运算的应用非常广泛,比如在搜索引擎、拼写检查、语言翻译、数据压缩等应用中,都需要进行串匹配。

BF(Brute-Force)算法

模式匹配不一定是从主串的第一个位置开始,可以指定主串中查找的起始位置pos。如果采用字符串顺序存储结构,可以写出不依赖于其他串操作的匹配算法。

  1. 分别利用计数指针i和j指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j初值为1。

  2. 如果两个串均未比较到串尾,即i和j均分别小于等于S和T的长度时,则循环执行以下操作:

    - S[i].ch和T[j].ch 比较,若相等,则i和j分别指示串中下个位置,继续比较后续字符;- 若不等,指针后退重新开始匹配,从主串的下一个字符( i=i-j+2)起再重新和模式的第一个字符(j=1 )比较。
    
  3. ③如果j>T.length,说明模式T中的每个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,返回和模式T中第一个字符相等的字符在主串S中的序号( i-T.length );否则称匹配不成功,返回0。

int Index(SString S, SString T, int pos)
{i = pos;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]; //匹配成功elsereturn 0; //匹配失败
}

BF算法实例:

/***字符串匹配算法***/
#include <cstring>
#include <iostream>
using namespace std;#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define MAXSTRLEN 255                //用户可在255以内定义最长串长
typedef char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度Status StrAssign(SString T, char *chars)
{ //生成一个其值等于chars的串Tint i;if (strlen(chars) > MAXSTRLEN)return ERROR;else{T[0] = strlen(chars);for (i = 1; i <= T[0]; i++)T[i] = *(chars + i - 1);return OK;}
}// BF算法
int Index(SString S, SString T, int pos)
{//返回模式T在主串S中第pos个字符之后第s一次出现的位置。若不存在,则返回值为0//其中,T非空,1≤pos≤StrLength(S)int i = pos;int 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];elsereturn 0;return 0;
} // Indexint main()
{SString S;StrAssign(S, "bbaaabbaba");SString T;StrAssign(T, "abb");cout << "主串和子串在第" << Index(S, T, 1) << "个字符处首次匹配\n";return 0;
}

该算法时间复杂度为:O(n*m)

KMP算法

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

  1. 分别利用计数指针i和j指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j初值为1。

  2. 如果两个串均未比较到串尾,即i和j均分别小于等于S和T的长度时,则循环执行以下操作:

    - S[i].ch 和T[i].ch 比较,若相等,则i和j分别指示串中下个位置,继续比较后续字符;
    - 若不等,指针后退重新开始匹配,从主串的下一个字符(i=i-j+2)起再重新和模式的第一个字符(j=1)比较。
    
  3. 如果j>T.length,说明模式T中的每个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,返回和模式T中第一个字符相等的字符在主串S中的序号(i-T.length);否则称匹配不成功,返回0。

该算法思想感兴趣可观看天勤公开课讲解视频,个人认为讲解比较透彻:

「天勤公开课」KMP算法易懂版

以下对其进行简单阐述:

kmp算法作用是快速的从一个主串中找到想要的子串,kmp算法可以做到仅仅后移模式串,比较指针不回溯。


找出模式串不匹配处最长但不长于不匹配处左端的公共前后缀,直接移动模式串,使得前缀直接来到后缀的位置。




int Index_KMP(SString S, SString T, int pos)
{i = pos;j = 1;                                 //初始化while (i <= S.length && j <= S.length) //两个串均未比较到串尾{if (j == 0 || S[i] == T[j]){++i;++j;} //继续比较后继字符else{j = next[j];} //模式串向右移动}if (j > T[0])return i - T[0]; //匹配成功elsereturn 0; //匹配失败
}
void get_next(SString T, int next[])
{ //求模式串T的next函数值并存入数组nexti = 1;next[1] = 0;j = 0;while (i < T[0]){if (j == 0 || T[i]){++i;++j;next[i] = j;}else{j = next[j];}}
}

KMP算法实例:

/***字符串匹配算法***/
#include <cstring>
#include <iostream>
using namespace std;#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define MAXSTRLEN 255                //用户可在255以内定义最长串长
typedef char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度Status StrAssign(SString T, char *chars)
{ //生成一个其值等于chars的串Tint i;if (strlen(chars) > MAXSTRLEN)return ERROR;else{T[0] = strlen(chars);for (i = 1; i <= T[0]; i++)T[i] = *(chars + i - 1);return OK;}
}
//算法4.3 计算next函数值
void get_next(SString T, int next[])
{ //求模式串T的next函数值并存入数组nextint i = 1, j = 0;next[1] = 0;while (i < T[0])if (j == 0 || T[i] == T[j]){++i;++j;next[i] = j;}elsej = next[j];
} // get_next// KMP算法
int Index_KMP(SString S, SString T, int pos, int next[])
{ // 利用模式串T的next函数求T在主串S中第pos个字符之后的位置的KMP算法//其中,T非空,1≤pos≤StrLength(S)int i = pos, j = 1;while (i <= S[0] && j <= T[0])if (j == 0 || S[i] == T[j]) // 继续比较后继字{++i;++j;}elsej = next[j]; // 模式串向右移动if (j > T[0])        // 匹配成功return i - T[0];elsereturn 0;
} // Index_KMPint main()
{SString S;StrAssign(S, "aaabbaba");SString T;StrAssign(T, "abb");int *p = new int[T[0] + 1]; // 生成T的next数组get_next(T, p);cout << "主串和子串在第" << Index_KMP(S, T, 1, p) << "个字符处首次匹配\n";return 0;
}

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


2、数组

①.数组的定义

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

②.数组的顺序存储

由于数组一般不做插人或删除操作,也就是说;一旦建立了数组,则结构中的数据元素个数和元素之间的关系就不再发生变动。因此,采用顺序存储结构表示数组比较合适。
二维数组可有两种存储方式:一种是以列序为主序的存储方式;一种是以行序为主序的存储方式。

三维数组的顺序存储:


③.特殊矩阵的压缩存储

特殊矩阵: 值相同的元素或0元素在矩阵中的分布有一定的规律。如:对称矩阵、三角矩阵、对角矩阵等。

压缩存储: 压缩存储是指为多个值相同的元只分配一个存储空间,且对零元不分配存储空间。目的是节省大量存储空间。

什么样的矩阵能够压缩?
特殊矩阵、稀疏矩阵等。

对称矩阵

若n阶矩阵A中的元满足aij = aji 1≤i,j≤ n 则称为n阶对称矩阵。对于对称矩阵,可以为每一对对称元分配一个存储空间,则可将n2个元压缩存储到n(n+1)/2个元的空间中,不失一般性,可以行序为主序存储其下三角(包括对角线)中的元。
假设以一维数组sa[r(n+1)/2]作为n阶对称矩阵A的存储结构,则 sa[k]和矩阵元aj之间存在着一一对应的关系:

三角矩阵

以主对角线划分,三角矩阵有上三角矩阵和下三角矩阵两种。上三角矩阵是指矩阵下三角(不包括对角线)中的元均为常数c或零的n阶矩阵,下三角矩阵与之相反。对三角矩阵进行压缩存储时,除了和对称矩阵一样,只存储其上(下)三角中的元素之外,再加一个存储常数c的存储空间即可。

上三角矩阵
sa[k]和矩阵元aij之间的对应关系为:

下三角矩阵
sa[k]和矩阵元aij之间的对应关系为:

对角矩阵

对角矩阵所有的非零元都集中在以主对角线为中心的带状区域中,即除了主对角线上和直接在对角线上、下方若干条对角线上的元之外,所有其他的元皆为零。


3、广义表

①.广义表的定义

广义表:n ( ≥0)个表元素组成的有限序列,记作LS=(a1,a2,…,an)。LS是表名,ai是表元素,它可以是表(称为子表),可以是数据元素(称为原子)。n为表的长度,n=0的广义表称为空表。

求表头GetHead(L):非空广义表的第一个元素,可以是一个原子,也可以是一个子表。
求表尾GetTail(L):非空广义表除去表头元素以外其它所有元素所构成的表。表尾一定是一个表。

②.广义表的存储结构

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

头尾链表的存储结构

由于广义表中的数据元素可能为原子或广义表,由此需要两种结构的结点:一种是表结点,用以表示广义表;一种是原子结点,用以表示原子。从上节得知:若广义表不空,则可分解成表头和表尾,因此,一对确定的表头和表尾可唯一确定广义表。一个表结点可由3个域组成:标志域、指示表头的指针域和指示表尾的指针域。而原子结点只需两个域:标志域和值域。

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是表结点的指针域,prt.hp和ptr.tp分别指向表头和表尾};
} * GList, GLNode; /* 广义表类型 */

扩展线性链表的存储结构

在这种结构中,无论是原子结点还是表结点均由三个域组成

typedef struct glnode
{int tag; // 0 原子结点;1 子表结点union{atomtype atom;     //原子结点的值域struct glnode *hp; //子表表头指针} struct glnode *tp;   //下一元素指针
} * glist;

4、总结

  1. 串是内容受限的线性表,它限定了表中的元素为字符。串有两种基本存储结构:顺序存储和链式存储,但多采用顺序存储结构。串的常用算法是模式匹配算法,主要有BF算法和KMP算法。BF算法实现简单,但存在回溯,效率低,时间复杂度为O(m ×n)。KMP算法对BF算法进行改进,消除回溯,提高了效率,时间复杂度为O(m+n)。

  2. 多维数组可以看成是线性表的推广,其特点是结构中的元素本身可以是具有某种结构的数据,但属于同一数据类型。一个n维数组实质上是n个线性表的组合,其每一维都是一个线性表。数组一般采用顺序存储结构,故存储多维数组时,应先将其确定转换为一维结构,有按“行”转换和按“列”转换两种。科学与工程计算中的矩阵通常用二维数组来表示,为了节省存储空间,对于几种常见形式的特殊矩阵,比如对称矩阵、三角矩阵和对角矩阵,在存储时可进行压缩存储,即为多个值相同的元只分配一个存储空间,对零元不分配空间。

  3. 广义表是另外一种线性表的推广形式,表中的元素可以是称为原子的单个元素,也可以是一个子表,所以线性表可以看成广义表的特例。广义表的结构相当灵活,在某种前提下,它可以兼容线性表、数组、树和有向图等各种常用的数据结构。广义表的常用操作有取表头和取表尾。广义表通常采用链式存储结构:头尾链表的存储结构和扩展线性链表的存储结构。


5、例题与应用


数据结构(串、数组和广义表)相关推荐

  1. [数据结构与算法] 串,数组和广义表

    串偏向于算法,数组和广义表偏向于理解 第四章 串.数组和广义表 4.1 串的定义 4.2 案例引入 4.3 串的类型定义,存储结构及运算 4.3.1 **串的类型定义** 4.3.2 串的存储结构 4 ...

  2. 数据结构05数组和广义表

    第五章 数组 和 广义表 数组和广义表可以看成是线性表在下述含义上的扩展:表中的数据元素本身也是一个数据结构. 5.1 数组的定义 n维数组中每个元素都受着n个关系的约束,每个元素都有一个直接后继元素 ...

  3. 数据结构之数组与广义表

    目录 联系 数组 广义表 联系 数组和广义表可看作一种扩展的线性数据结构,其特殊性在于数据元素的构成上.从组成线性表的元素角度看,数组是由具有某种结构的数据元素构成,广义表则是由单个元素或子表构成的. ...

  4. 数据结构:数组和广义表

    数组 数组这部分内容在写<线性表>的的时候介绍过,所以这里就略过一部分内容(略过的内容在这里),写一写前边没写过的. 由于数组中各元素具有统一的类型,并且数组元素的下标一般具有固定的上界和 ...

  5. C语言数据结构学习——数组和广义表

    数组和广义表 数组 数组定义 特点 常见运算及声明方式 数组的顺序表示和实现 矩阵的压缩存储 概念 稀疏矩阵 对称矩阵 三角矩阵 广义表 数组 数组定义 数组(Array)是有序的元素序列.若将有限个 ...

  6. 六、考研数据结构笔记——数组与广义表

    一.数组的定义 一维数组:就是线性表,前面有 二维数组:准备考研的应该都知道,就是一个矩阵. 数组一旦被定义其,维数(行)和维界(列)就不能改变.只会读取加修改元素. 二.二维数组的按行(列)优先 事 ...

  7. 【数据结构】数组和广义表

    感觉数组这一段没讲什么太多的东西. 先是讲了下定义,就是每个维度上都有对应的前驱后继,首尾元素例外.操作只有初始化 销毁 取元素 修改元素.然后讲了下适合用顺序存储结构,多维情况下根据下标(j1 j2 ...

  8. 【数据结构总结】第四章:串、数组和广义表(线性结构)

    第四章:串.数组和广义表(线性结构) 提示:本文主要是以思维导图的形式概括数据结构第一章的精华内容,基本不会用到文字性的内容,目的是为了给大家梳理每个重要的知识点的相关概念,方便大家在复盘的时候快速阅 ...

  9. 《数据结构》-第四章 串、数组和广义表(习题)

    第四章 串.数组和广义表练习题 本章考点较少易于掌握,对于串的重点考点为串的模式匹配算法:数组的主要考点为数组下标与存储地址计算和特殊矩阵的压缩存储方法:针对广义表的考点主要为在广义表中取原子项(表) ...

  10. 数据结构:串、数组和广义表

    串 线性结构:线性表.栈和队列.串与数组和广义表 串的逻辑结构和线性表极为相似,区别仅在于串的数据对象限定为字符集.在基本操作上,串和线性表有很大差别.线性表的基本操作主要以单个元素作为操作对象,如查 ...

最新文章

  1. 影响线型缩聚物分子量的因素_【CRPS】通过硼烷引发剂实现氧气引发的超高分子量聚合物的可控合成...
  2. ajaxsetup无效_Ajax请求session失效该如何解决
  3. python27安装教程-Python2和Python3安装教程
  4. mysql 多列索引的生效规则
  5. OTA江湖浪潮再起,世界邦的出境定制自由行之路难以亨通?
  6. 使用 JavaScript 进行 Base64 编码与解码
  7. 从源码剖析SpringBoot中Tomcat的默认最大连接数
  8. .NET CORE(C#) WPF简单菜单MVVM绑定
  9. 利用 dbghelp.dll 生成 dump 文件
  10. iPhone X Web 设计
  11. gcc编译的几个重要参数
  12. HNU 实验五 猴导师
  13. 简单六步上手spring aop,通过各种类型通知,面向切面编程,实现代码解耦(超详细)
  14. java 实现poi方式读取word文件内容
  15. latex字母/数字双线字体
  16. Python爬取当当网图书数据
  17. 小米格式化fastboot_小米fastboot刷机教程
  18. 如何清空matlab命令窗口,matlab如何清空命令窗口中的内容
  19. “追光者谢崇进:如何让光通信极速飙车?
  20. 高级项目经理资质论文

热门文章

  1. Jmeter调用第三方jar包,配合BeanShell使用
  2. 有照片换发型的软件吗?看完这篇文章就知道了
  3. 转载:报表软件比较参考
  4. 警惕苏宁电器分期(招行信用卡)陷阱!!
  5. STI、LOD与WPE概念:WPE效应对SPICE Model 的影响
  6. 微信unionid的获取踩到的坑
  7. 【ML31】Advanced K-means clustering algorithm
  8. mysql 远程可以连接 localhost或者127.0.0.1 不能连接问题解决
  9. 压缩包里的文件名可以这样隐藏起来
  10. 夺命雷公狗—玩转SEO---65---黑阔手法秒杀dede提升权重