串、数组和广义表

114 阅读7分钟

串、数组和广义表

是内容受限的线性表:规定每个数据元素只能是字符

数组和广义表线性结构的推广——非线性结构

串 string

定义

零个或多个任意字符组成的有限序列

S=a1a2a3an(n0)S = a_1a_2a_3……a_n (n\geqslant0)
  • S:串名
  • a1a2……an:串值
  • n:串长,n=0 时称为空串,用Φ表示

术语

  • **字串:**一个串中任意个连续字符组成的子序列(含空串)
  • **真字串:**不包含自身的所有子串
  • **主串:**包含字串的串
  • **字符位置:**字符在序列中的序号
  • **子串位置:**子串第一个字符在主串中的位置
  • **空格串:**由一个或多个空格组成的串
  • **串相等:**两个串长度相等,且各个位置上对应的字符相等

**注:**所有空串都相等

应用

  • 病毒感染检测

类型定义

ADT String{
    数据对象:D = {ai | ai ∈ CharacterSet, i = 1,2,…,n, n≥0}
    数据关系:R1 = {<ai-1,ai> | ai-1, ai ∈D, i=1,2,…,n}
    基本操作:
        StrAssign(&T,chars)			// 串赋值
        StrCompare(S)				// 串比较
        StrLength(S)				// 求串长
        Concat(&T,S1,S2)			// 串连结
        SubString(&Sub,S,pos,len)	// 求子串
        StrCopy(&T,S)				// 串拷贝
        StrEmpty(S)					// 串判空
        ClearString(&S)				// 清空串
        Index(S,T,pos)				// 求子串位置
        Replace(&S,T,V)				// 串替换
        StrInsert(&S,pos,T)			// 子串插入
        StrDelete(&S,pos,len)		// 子串删除
        DestroyString(&S)			// 串销毁
}ADT String

串的顺序存储结构

// 较链式存储,顺序存储更多
#define MAXLEN 255
typedef struct{
    char ch[MAXLEN+1];	// 存储串的一维数组
    int length;			// 串的当前长度
}SString;

串的链式存储结构

块链结构

块链
#define CHUNKSIZE 80	// 定义块的大小
typedef struct Chunk{
    char ch[CHUNKSIZE];
    struct Chunk *next;
}Chunk;

typedef struct{
    Chunk *head, *tail;	// 串的头指针和尾指针
    int curlen;			// 串的当前长度
}LString;				// 字符串的块链结构

串的模式匹配算法

**算法目的:**确定主串中所含子串(模式串)第一次出现的位置(定位)

**应用:**搜索引擎、拼写检查、语言翻译、数据压缩……

算法种类:

  • BF算法(Brute-Force,古典的,经典的,穷举的,朴素的)
  • KMP算法(速度快)

BF 算法

匹配失败后回溯i = i-j+2; ,理解为:i = (i-j+1) + 1

算法思想: Index(S, T, pos)

  1. 将主串的第 pos 个字符和模式串的第一个字符比较,
  2. 若相等,继续逐个比较后续字符;
  3. 若不等,从主串的下一个字符开始,重新与模式串的第一个字符比较
    • 直到主串的一个连续子串序列与模式串相等;返回值为 S 中与 T 匹配的子序列第一个字符的序号,即匹配成功;
    • 否则,匹配失败,返回0
int Index_BF(SString S, SString T){
    int i = 1, 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;
    }
}
// 添加查找位置 pos
int Index_BF(SString S, SString T, int pos){
    int 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;
    }
}
// 时间复杂度:若 m<<n,则为 O(n*m)
BF算法时间复杂度

KMP 算法

KMP 算法是由 D.E.Knuth、J.H.Morris、V.R.Pratt 共同提出

这一算法较BF 算法,算法效率得到显著提高

算法思想:

  • 利用已经部分匹配的结果,来加快模式串的滑动速度
  • 主串 S 的指针 i 不必回溯,可提速到 O(n+m)

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

next 数组 next数组举例
// 求 next 数组
void Get_next(SString T, int &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];
    }
}

KMP 算法

// KMP 算法
int Index_KMP(SString S, SString T, int pos){
    int 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];	// j 不变,i 后退
        if(j > T.length)
            return i-T.length;
        else 
            return 0;
    }
}

next 数组改进——nextval

nextval
void get_nextval(SString T, int &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 j = nextval[j];
    }
}

数组

按一定格式排列起来,具有相同类型的数据元素的集合

二维数组定义:

typedef elemtype array2[m][n];

等价于

typedef elemtype array1[n];

typedef array1 array2[m];

线性表结构是数组结构的一个特例,数组结构是线性表结构的扩展

抽象数据类型定义

数组抽象数据类型定义

较为复杂,以二维数组举例

基本操作

基本操作

数组的顺序存储

很少采用链式存储结构,因为数组的特点是结构固定,维数和维界不变;一般很少做插入与删除操作

所以,一般采用顺序存储结构来表示数组

**注:**数组可以多维,但存储数据元素的内存单元地址是一维的;因此需要将多为关系映射到一维关系

二维数组有两种存储方式

  • 以行序为主
  • 以列序为主

二维数组A[m][n] ,以行序为主序时,数组中任意元素 a[i][j] 的存储位置为:

LOC(i,j)=LOC(0,0)+(ni+j)LLOC(i,j)=LOC(0,0)+(n*i+j)*L

特殊矩阵的压缩存储

**矩阵常规存储:**将矩阵描述为二维数组,其特点为

  • 可以对元素随机存取
  • 运算简单
  • 存储密度为1

对称矩阵

在 n*n 的矩阵 a 中,满足:延主对角线对称

aij=ajia_{ij}=a_{ji}

存储方法:

  • 只存储下(或者上)三角,包括对角线的数据元素

共占用 n*(n+1)/2 个元素空间;

  • 以行序为主序将元素存放在一个一维数组中
  • 任一元素 aij 的存储位置为 i*(i-1)/2 + j - 1

三角矩阵

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

存储方法:

重复元素 c 共享一个元素存储空间,共占用 n*(n+1)/2+1 个元素空间,具体存储位置的计算:

上下三角矩阵存储位置计算公式

对角矩阵(带状矩阵)

所有非零元素集中在以主对角线为中心的带状区域,其他位置值为0

存储方法:

以对角线的顺序存储,存为二维数组

稀疏矩阵

在 m*n 的矩阵中,非零元素个数为 t,若:

δ=tmnδ=\frac{t}{m*n}

δ≤0.05 时称该矩阵为稀疏矩阵

存储方法:

  • 由三元组 (i, j, aij) 唯一确定矩阵的一个非零元

  • 存储矩阵的行数、列数、非零元素总个数

三元组顺序表法
三元组顺序表法

它又称 有序的双下标法

  • **优点:**非零元在表中按行序有序存储,便于进行依行顺序处理的矩阵运算
  • **缺点:**不能随机存取,若按行存取某一行中的非零元,需要从头查找
链式存储:十字链表法
  • **优点:**灵活地插入运算产生的新非零元素;删除因运算产生的新的零元素

十字链表中,矩阵的每一个非零元素用一个结点表示,该节点除了 (row, col, value) 外,还有两个域:

  • **right:**链接同一行中下一个非零元素
  • **down:**链接同一列中下一个非零元素
十字链表法

广义表

线性表的推广

它是 n 个元素 a1,…,an 的有限序列,其中每一个 ai 或者是原子,或者是另一个广义表

  • 广义表通常记作:LS = ( a1,…,an

    • LS 表名
    • n 表的长度
    • ai 表的元素
  • 习惯用大写字母表示广义表,小写字母表示原子

  • **表头:**若 LS 非空,则它的第一个元素 ai 就是表头 记作 head(LS) = ai **注:**表头可以是元素,也可以是子表

  • **表尾:**除表头以外的其它元素组成的表 记作 tail(LS) =( a2,…,an **注:**表尾不是最后一个元素,而是一个子表

性质
  1. 广义表的数据元素有相对次序:一个直接前驱和一个直接后继
  2. 广义表的长度定义为最外层包含的元素个数 例: C=(a, (b, c)),它是长度为 2 的广义表
  3. 广义表的深度定义为该广义表展开后所包含的括号重数 A=(b, c) 深度为1 B=(A, d) 深度为2 ”原子“的深度为0;”空表”的深度为1
  4. 广义表可以为其它表共享,例如上面:广义表 B 共享表 A
  5. 广义表可以是一个递归的表,例:F=(a, F) 注:递归表的深度无穷,长度为有限值
  6. 广义表是多层次结构,广义表的元素可以是单元素,也可以是子表;而子表的元素还可以是子表……
基本运算
  1. 求表头 GetHead(L) 非空广义表的第一个元素,可以是一个单一元素,也可能是一个子表
  2. 求表尾 GetTail(L) 非空广义表表尾一定是一个表 是除去表头元素以外的其它元素所构成的表