数据结构(严蔚敏版)—第四章《串、数组和广义表》

285 阅读5分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 二十六 天,点击查看活动详情

第四章__串、数组和广义表

4.1、串的定义

串(String):零个或多个任意字符组成的有限序列

1、关于串的术语

  • 子串:一个串中任意个连续字符组成的子序列(含空串)称为该串的子串

    • 真子串: 不包含自身的所有子串
  • 主串:包含子串的串相应的称为主串

  • 字符位置: 字符在序列中的序号为该字符在串中的位置

  • 子串位置:子串第一个字符在主串中的位置

  • 串相等: 当且仅当两个串的长度相等并且各对应位置上的字符都相同时,这两个串才相等

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

4.2.1、串的抽象类型定义

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

ADT String {
  数据对象: D = { ai | ai 属于 CharatcterSet, i = 1, 2, ... ,n, n>=0}
  数据关系: R1 = {<ai-1, ai> | ai - 1, ai 属于 D, i = 2, ..., n}
  基本操作:
    StrAssign(&T chars);
        初始条件: chars时字符串常量
      操作结果: 生成一个其值等于chars的串T
    StrCopy(&T, S);
        初始条件: 串S存在。
      操作结果: 由串S复制得串T
    StrEmpty(S);
        初始条件: 串S存在。
      操作结果: 若S为空串,则返回true,否则返回false
    StrCompare(S, T);
        初始条件: 串S和T存在
      操作结果: 若S > T 则返回值 > 0, 若S = T,则返回值=0, 若S < T, 则返回值 < 0
    StrLength(S);
        初始条件: 串S存在。
      操作结果: 返回S的元素个数,称为串的长度
    ClearString(&S);
        初始条件: 串S存在。
      操作结果: 将S清为空串。
    Concat(&T, S1, S2);
        初始条件: 串S1和S2都存在
      操作结构: 用T返回由S1和S2联接而成的新串
    Index(S, T, pos);
        初始条件: 串S和串T存在,T是非空串, 1 <= pos <= StrLength(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) - len + 1
      操作结果: 从串S中删除第pos个字符起长度为len的子串
    DestroyString(&S);
            初始条件: 串S存在
      操作结果: 串S被销毁
}ADT String

4.2.2、串的存储结构

串也有两种存储结构:顺序存储和链式存储,考虑到存储效率和算法的方便性,串多采用顺序存储。

1、串的顺序存储结构

串的顺序存储结构定义:

#define MAXLEN 255
typedef struct {
  char ch[MAXLEN + 1];      // 存储串的一位数组
  int length;                       // 串当前的长度
}SString;

2、串的链式存储结构——块链结构

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

4.2.3、串的模式匹配算法

  • BF算法(Brute-Force,又称古典的、经典的、朴素的、穷举的)
  • KMP算法(特点:速度快)

BF算法

  • Brute-Force简称为BF算法,也称简单匹配算法。
  • 思路:从S的第一个字符开始依次与T的字符进行匹配

【算法步骤】

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

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

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

【算法描述】

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;                                                    // 匹配失败
}

【算法过程演示】

下图展示模式T = "abcac" 和主串S的匹配过程(pos = 1)

IMG_20221021_160155

4.3、数组

  • 数组:按一定格式排列起来的,具有相同类型的数据元素的集合。
  • 一维数组:若线性表中的数据元素为非结构的简单元素,则称为一维数组
  • 一维数组的逻辑结构:线性结构。定长的线性表。
  • 声明格式:数据类型 变量名称[长度];
  • 二维数组:若一维数组中的元素又是一维数组结构,则称为二维数组
  • 二维数组的逻辑结构:非线性结构、线性结构
  • 声明格式: 数据类型 变量名称[行数] [列数];

4.3.1、数组的抽象数据类型定义:

image-20221021195221193

image-20221021195232535

4.3.2、数组的顺序存储结构

  • 数组特点:结构固定——维数和维界不变
  • 数组基本操作:初始化、销毁、取元素、修改元素值
一维数组

LOC(i) = LOC(i-1)+l = a + i * l, i > 0

其中:l 是每个元素所占的存储单元,i 是元素的位置

例:有数组定义:int a[5] 每个元素占用4个字节,假设a[0]存储在2000单元,a[3]地址是多少?

LOC(0) = a = 2000 L = 4

LOC(3) = a + 3 * 4 = 2012

LOC(i) = a + i * L

二维数组

设数组开始存放位置 LOC( 0, 0 ) = a ,LOC ( j, k ) = a + j * m + k

image-20221021201945018

image-20221021202004912

以行序为主序:

设数组开始存储位置LOC(0, 0),存储每个元素需要L个存储单元

数组元素a[i] [j]的存储位置是:LOC(i, j) = LOC(0, 0) + (n * i + j) * L

image-20221023225858775

例题:

image-20221021204625752

三维数组

a[m1] [m2] [m3] 各维元素个数为m1, m2, m3

下标为i1, i2, i3 的元素的存储位置:

LOC(i_1,i_2, i_3) = a + i_1 * m_2 * m_3 + i_2 * m_3 + i_3

数组特点:结构固定——维数和维界不变

数组基本操作:初始化、销毁、取元素、修改元素值。

4.3.3、特殊矩阵的压缩存储

矩阵: 一个由m * n 个元素拍成的m行n列的表

矩阵的常规存储: 将矩阵描述为一个二维数组。

矩阵常规存储的特点:

  • 可以对其元素进行随机存取
  • 矩阵元素非常简单;存储的密度为1

不适合常规存储的矩阵: 值相同的元素很多且呈现某种规律分布;零元素多。

矩阵的压缩存储: 为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。

1、对称矩阵

【特点】

在 n × n的矩阵a中,满足如下性质:aij = aji, (1 <= i, j <= n)

【存储方法】

只存储下(或者上)三角(包括主对角线)的数据元素。共占用n(n + 1) / 2个元素空间

【对称矩阵的存储结构】

对称矩阵上下三角中的元素数均为:n(n + 1) / 2,可以以行序为主序将元素存放在一个一维数组sa[n(n + 1) / 2]中

求aij的在一维数组中的位置:aij = a_k = i (i - 1) / 2 + (j - 1)

2、三角矩阵

【特点】

对角线以下(或者以上)的数据元素(不包括对角线)全部为常数c。

image-20221024103351711

【存储方法】

重复元素c共享一个元素存储空间,共占用n(n + 1) / 2 + 1个元素

image-20221024103456617

3、对角矩阵的存储(带状矩阵)

【特点】

在n * n的在方阵中,所有非零元素都集中在以对角线为中心的带状区域中,区域外的值全为0,则称为对角矩阵。

【存储方法】

image-20221024105143283

4、稀疏矩阵存储

稀疏矩阵: 设在m * n 的矩阵中有t个非零元素。令 a = t / (m * n) 当a <= 0.05时称为稀疏矩阵

我们采用三元组的方式进行存储

image-20221024110707647

例:试还原出下列三元组所表示的系数矩阵。

image-20221024110951493

三元组顺序表又称:有序的双下标法

三元组顺序表的优缺点:

  • 优点:非零元素在表中按行有序存储,因此便于进行以行顺序处理的矩阵运算
  • 缺点:不能随机存取。若按行号存取某一行的非零元,则需从头开始进行查找。

4.4、广义表

定义: 广义表(又称列表Lists)是n >= 0个元素a_0, a_1, ... ,a(n-1)的有限序列,其中每一个ai或者原子,或者一个广义表。

广义表: LS=(a_1, a_2, ..., a_n),其中LS为表名,n为表的长度,每个ai为表的元素。

表头: 若LS非空(n >= 1),则其第一个元素a1就是表头。记作head(LS) = a1 注意:表头可以是原子,也可以是子表。

表尾: 除表头之外的其他元素组成的表。记作 tail(LS) = (a_2, ..., a_n) 注意:表尾不是最后一个元素,而是一个子表。

例题:

image-20221024113455286

广义表的性质

  • 有次序性:一个直接前驱和一个直接后继
  • 有长度:=表中元素个数。例如: C= (a, (b, c))
  • 有深度:=表中括号的重数 原子的深度为0, 空表的深度为1
  • 可递归:自己可以作为自己的子表
  • 可共享:可以为其他广义表所共享
  • 多层次结构:广义表的元素可以是单元素,也可以是子表

广义表的基本运算

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

image-20221024132652469