《数据结构 C语言版 严蔚敏 第2版》:串、数组、广义表

一、串

1、串的定义 

计算机上的非数值处理的对象大部分是字符串数据,字符串一般简称为串


串是一种特殊的线性表,其特殊性体现在数据元素是一个字符,也就是说,串是一种内容
受限的线性表


串(string)(或字符串)是由零个或多个字符组成的有限序列


串中字符的数目 n 称为串的长度。零个字符的串称为空串(null string) , 其长度为零


只有当两个串的长度相等, 并且各个对应位置的字符都相等时才相等


由一个或多个空格组成的串 " " 称为空格串 (blank string, 请注意:此处不是空串),
其长度为串中空格字符的个数

2、串的存储结构及其运算 

2.1、串的存储结构 

1)串的顺序存储 

用一组地址连续的存储单元存储串值的字符序列。 

按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用定长数
组如下描述: 

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

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

串的堆式顺序存储结构 

typedef struct{
    char *ch; // 若是非空串,则按串长分配存储区,否则 ch 为NULL
    int length; // 串的当前长度
} HString;

2)串的链式存储 

 用链表存储串值时,存在一个“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符

 

(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;

在链式存储方式中,结点大小的选择直接影响着串处理的效率。

 一般来说,字符集小,则字符的机内编码就短,这也影响串值存储方式的选取。

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

2.2、串的模式匹配算法 

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

串的模式匹配设有两个字符串S和T, 设S为主串,也称正文串;设T为子串,也称为模式。 

1)BF 算法 

最简单直观的模式匹配算法是 BF (Brute-Force) 算法


最好情况下的平均时间复杂度是 O(n + m)


最坏情况下的平均时间复杂度是 O(n * m) 

步骤: 

  1. 分别利用计数指针 i 和 j 指示主串 S 和模式 T 中当前正待比较的字符位置,i 初值为 pos,j 初值为 1
  2. 如果两个串均未比较到串尾,即 i 和 j 均分别小于等于 S 和 T 的长度时,则循环执行以下操作:
    1. S[i].ch 和 T[j].ch 比较,若相等,则 i 和 j 分别指示串中下个位置,继续比较后续字符
    2. 若不等,指针后退重新开始匹配,从上一次匹配主串的起始位置的下一个字符(i = i - j + 2)起再重新和模式的第一个字符(j = 1)比较
  3. 如果 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[i].ch == T[j].ch){ // 继续比较后继字符
            ++i;
            ++j;
        }else{ // 指针后退重新开始匹配
            i = i - j + 2; // 上一次匹配主串的起始位置的下一个字符
            j = 1;
        }
    }
    if(j > T.length)
        return i - T.length; // 匹配成功
    else
        return 0; // 匹配失败
}

 BF 算法的匹配过程

2)KMP 算法 

这种改进算法是由 Knuth 、 Morris 和 Pratt 同时设计实现的, 因此简称 KMP 算法。

此算法可以在 O(n + m) 的时间数量级上完成串的模式匹配操作。 

改进内容:
每当一趟匹配过程中出现字符比较不等时,不需回溯 i 指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。


当匹配过程中产生 “失配” 时,指针 i 不变,指针 j 退回到 next[j] 所指示的位置上重新进行比较,并且当 指针 j 退至零时,指针 i 和指针 j 需同时增 1。即若主串的第 i 个字符和模式的第 1 个字符不等,应从主串的第 i + 1个字符起重新进行匹配。 

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[i] == T[j]){ // 继续比较后继字符
            ++i;
            ++j;
        }else{ // 模式串向后移动
            j = next[j];
        }
    }
    if(j > T[0])
        return i - T[0]; // 匹配成功
    else
        return 0; // 匹配失败
}

计算 next 函数值 

// 时间复杂度为O(m)。
// 通常,模式串的长度m比主串的长度n要小得多,对整个匹配算法来说,所增加的
这点时间是值得的。
void get_next(SString T,int next[]){
    // 求模式串 T 的 next 函数值并存入数组 next
    i = 1;
    next[1] = 0;
    j = 0;
    while(i < T[0]){
        if(j == 0 || T[i] == T[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[0]){
        if(j == 0 || T[i] == T[j]){
            ++i;
            ++j;
            if(T[i] != T[j]){
                nextVal[i] = j;
            }else{
                nextVal[i] = nextVal[j];
            }
        }else{
            j = nextVal[j];
        }
    }
}

 

二、数组

 1、数组的定义

数组是由类型相同的数据元素构成的有序集合。


数组中每个元素处于 n(n ≥ 1) 个关系中,故称该数组为 n 维数组。


特点是结构中的元素本身可以是具有某种结构的数据, 但属于同一数据类型。 

 

2、特殊矩阵的压缩存储 

所谓压缩存储,是指为多个值相同的元只分配一个存储空间, 对零元不分配空间。

假若值相同的元素或者零元素在矩阵中的分布有一定规律,则称此类矩阵为特殊矩阵。 

1)对称矩阵 

若 n 阶矩阵A中的元满足下述性质 a_{ij} = a_{ji} 1 ≤ i,j ≤ n 则称为 n 阶对称矩阵。
对于对称矩阵,可以为每一对对称元分配一个存储空间,则可将 n^{2} 个元压缩存储到 \frac{n(n +1)}{2} 个元的空间中。 

2)三角矩阵 

上三角矩阵是指矩阵下三角(不包括对角线)中的元均为常数 c 或零的 n 阶矩阵, 下三角矩阵与之相反。 

3)对角矩阵 

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

4)稀疏矩阵 

非零元较零元少,且分布没有一定规律, 称之为稀疏矩阵。 

 

三、广义表 

3.1、广义表的定义 

广义表是线性表的推广,也称为列表。 

在线性表的定义中,a_{i}(1 ≤ i ≤ n)只限于是单个元素。

而在广义表的定义中,ai可以是单个元素,也可以是广义表,分别称为广义表 LS 的原子和子表。

习惯上,用大写字母表示广义表的名称,用小写字母表示原子。

广义表的定义是一个递归的定义。 

例子:

(1) A = () —— A 是一个空表, 其长度为零

(2) B=(e) —— B 只有一个原子 e, 其长度为 1

(3) C= (a, (b, c, d)) —— C的长度为2, 两个元素分别为原子 a 和子表(b,c, d)。

(4) D = (A, B, C) —— D 的长度为3,3个元素都是广义表。显然,将子表的值代入后,则有 D = ((), (e), (a, (b, c, d)))。

(5) E = (a, E) —— 这是一个递归的表, 其长度为2。E 相当于一个无限的广义表 E=(a, (a,(a, ···)))。 

结论:

  • 广义表的元素可以是子表,而子表的元素还可以是子表。
  • 广义表可为其他广义表所共享。
  • 广义表可以是一个递归的表,即广义表也可以是其本身的一个子表。 

广义表最重要的两个运算:

  • 取表头 GetHead(LS):取出的表头为非空广义表的第一个元素,它可以是一个单原子,也可以是一个子表。
  • 取表尾 GetTail(LS):取出的表尾为除去表头之外,由其余元素构成的表。即表尾一定是一个广义表。 

 

3.2、广义表的存储结构 

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


常用的链式存储结构有两种, 头尾链表的存储结构 和 扩展线性链表的存储结构 。 

1)头尾链表的存储结构 

一对确定的表头和表尾可唯一确定广义表。


一个表结点可由3个域组成:标志域、 指示表头的指针域和指示表尾的指针域。


原子结点只需两个域 :标志域和值域。 

广义表的头尾链式存储结构 

typedef enum{
    ATOM, // ATOM == 0:原子
    LIST // LIST == 1:子表
} ElemTag;

typedef struct GLN ode{
    ElemTag tag; // 公共部分,用于区分原子结点和表结点
    union{
        AtomType atom; // atom是原子结点的值域,AtomType有用户定义
        struct{
            struct GLN ode *hp,*tp;
        }ptr; // ptr是表结点的指针域,ptr.hp和ptr.tp分别指向表头和表尾
    };
}*GList; // 广义表类型

 

  • 除空表的表头指针为空外,对任何非空广义表,其表头指针均指向一个表结点, 且该结点中的 hp 域指示广义表表头(或为原子结点,或为表结点),tp 域指向广义表表尾(除非表尾为空,则指针为空,否则必为表结点)
  • 容易分清列表中原子和子表所在层次
  • 最高层的表结点个数即为广义表的长度

 

2)扩展线性链表的存储结构 

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

扩展线性链表表示的存储结构示例 

一  叶  知  秋,奥  妙  玄  心 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
本课件是一个动态演示数据结构算法执行过程的辅助教学软件, 它可适应读者对算法的输入数据和过程执行的控制方式的不同需求, 在计算机的屏幕上显示算法执行过程中数据的逻辑结构或存储结构的变化状况或递归算法执行过程中栈的变化状况。整个系统使用菜单驱动方式, 每个菜单包括若干菜单项。每个菜单项对应一个动作或一个子菜单。系统一直处于选择菜单项或执行动作状态, 直到选择了退出动作为止。 本系统内含84个算法,分属13部分内容,由主菜单显示,与《数据结构》教科书中自第2章至第11章中相对应。各部分演示算法如下: 1. 顺序表 (1)在顺序表中插入一个数据元素(ins_sqlist) (2)删除顺序表中一个数据元素(del_sqlist) (3)合并两个有序顺序表(merge_sqlist) 2. 链表 (1)创建一个单链表(Crt_LinkList) (2)在单链表中插入一个结点(Ins_LinkList) (3)删除单链表中的一个结点(Del_LinkList) (4)两个有序链表求并(Union) (5)归并两个有序链表(MergeList_L) (6)两个有序链表求交(ListIntersection_L) (7)两个有序链表求差(SubList_L) 3. 栈和队列 (1)计算阿克曼函数(AckMan) (2)栈的输出序列(Gen、Perform) (3)递归算法的演示  汉诺塔的算法(Hanoi)  解皇后问题的算法(Queen)  解迷宫的算法(Maze)  解背包问题的算法(Knap) (4)模拟银行(BankSimulation) (5)表达式求值(Exp_reduced) 4. 的模式匹配 (1)古典算法(Index_BF) (2)求Next 函数值(Get_next)和按Next 函数值进行匹配 (Index_KMP(next)) (3)求 Next 修正值(Get_nextval)和按 Next 修正值进行匹配(Index_KMP(nextval)) 5. 稀疏矩阵 (1)矩阵转置 (Trans_Sparmat) (2)快速矩阵转置 (Fast_Transpos) (3)矩阵乘法 (Multiply_Sparmat) 6. 广义表 (1)求广义表的深度(Ls_Depth) (2)复制广义表(Ls_Copy) (3)创建广义表的存储结构(Crt_Lists) 7. 二叉树 (1)遍历二叉树  二叉树的线索化  先序遍历(Pre_order)  中序遍历(In_order)  后序遍历(Post_order) (2) 按先序建二叉树(CrtBT_PreOdr) (3) 线索二叉树  二叉树的线索化  生成先序线索(前驱或后继) (Pre_thre)  中序线索(前驱或后继) (In_thre)  后序线索(前驱或后继) (Post_thre)  遍历中序线索二叉树(Inorder_thlinked)  中序线索树的插入(ins_lchild_inthr)和删除(del_lchild_inthr)结点 (4)建赫夫曼树和求赫夫曼编码(HuffmanCoding) (5)森林转化成二叉树(Forest2BT) (6)二叉树转化成森林(BT2Forest) (7)按表达式建树(ExpTree)并求值(CalExpTreeByPostOrderTrav) 8. 图 (1)图的遍历  深度优先搜索(Travel_DFS)  广度优先搜索(Travel_BFS) (2)求有向图的强连通分量(Strong_comp) (3)有向无环图的两个算法  拓扑排序(Toposort)  关键路径(Critical_path) (4)求最小生成树  普里姆算法(Prim)  克鲁斯卡尔算法(Kruscal) (5)求关节点和重连通分量(Get_artical) (6)求最短路径  弗洛伊德算法(shortpath_Floyd)  迪杰斯特拉算法(shortpath_DIJ) 9. 存储管理 (1)边界标识法 (Boundary_tag_method) (2)伙伴系统 (Buddy_system) (3)紧缩无用单元 (Storage_compaction) 10. 静态查找 (1)顺序查找(Search_Seq) (2)折半查找 (Serch_Bin) (3)插值查找 (Search_Ins) (4)斐波那契查找 (Search_Fib) (5)次优查找树(BiTree_SOSTree) 11. 动态查找 (1)在二叉排序树上进行查找(bstsrch)、插入结点(ins_bstree)和删除结点(del_bstree) (2)在二叉平衡树上插入结点(ins_AVLtree) 和删除结点(del_AVLtree) (3)在 B-树上插入结点(Ins_BTree) 和 删除结点(Del_BTree) (4)在 B+树上插入结点(Ins_PBTree) 和 删除结点(Del_PBTree) 12. 内部排序 (1)简单排序法  直接插入排序(Insert_sort)  表插入排序(内含插入(Ins_Tsort) 重排(Arrange)两个算法)  起泡排序(BubbleSort)  简单选择排序(SelectSort) (2)复杂排序法  堆排序(HeapSort)  快速排序(QuickSort)  锦标赛排序(Tournament) (3)其他  快速地址排序(QkAddrst)  基数排序(RadixSort) 13. 外部排序 (1)多路平衡归并排序(K-Merge) (2)置换-选择排序(Repl_Selection)
数据结构》(C语言)是为“数据结构”课程编写的教材,也可作为学习数据结构及其算法的C程序设计的参数教材。学了数据结构后,许多以前写起来很繁杂的代码现在写起来很清晰明了. 本书的前半部分从抽象数据类型的角度讨论各种基本类型的数据结构及其应用;后半部分主要讨论查找和排序的各种实现方法及其综合分析比较。 全书采用类C语言作为数据结构和算法的描述语言。 本书概念表述严谨,逻辑推理严密,语言精炼,用词达意,并有配套出的《数据结构题集》(C语言),便于教学,又便于自学。 本书后附有光盘。光盘内容可在DOS环境下运行的以类C语言描述的“数据结构算法动态模拟辅助教学软件,以及在Windows环境下运行的以类PASCAL或类C两种语言描述的“数据结构算法动态模拟辅助教学软件”。内附 数据结构算法实现(严蔚敏配套实现程序) 目录: 第1章 绪论 1.1 什么是数据结构 1.2 基本概念和术语 1.3 抽象数据类型的表现与实现 1.4 算法和算法分析 第2章 线性表 2.1 线性表的类型定义 2.2 线性表的顺序表示和实现 2.3 线性表的链式表示和实现 2.4 一元多项式的表示及相加 第3章 栈和队列 3.1 栈 3.2 栈的应有和举例 3.3 栈与递归的实现 3.4 队列 3.5 离散事件模拟 第4章 4.1 类型的定义 4.2 的表示和实现 4.3 的模式匹配算法 4.4 操作应用举例 第5章 数组广义表 5.1 数组的定义 5.2 数组的顺序表现和实现 5.3 矩阵的压缩存储 5.4 广义表的定义 5.5 广义表的储存结构 5.6 m元多项式的表示 5.7 广义表的递归算法第6章 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.2.1 二叉树的定义 6.2.2 二叉树的性质 6.2.3 二叉树的存储结构 6.3 遍历二叉树和线索二叉树 6.3.1 遍历二叉树 6.3.2 线索二叉树 6.4 树和森林 6.4.1 树的存储结构 6.4.2 森林与二叉树的转换 6.4.3 树和森林的遍历 6.5 树与等价问题 6.6 赫夫曼树及其应用 6.6.1 最优二叉树(赫夫曼树) 6.6.2 赫夫曼编码 6.7 回溯法与树的遍历 6.8 树的计数 第7章 图 7.1 图的定义和术语 7.2 图的存储结构 7.2.1 数组表示法 7.2.2 邻接表 7.2.3 十字链表 7.2.4 邻接多重表 7.3 图的遍历 7.3.1 深度优先搜索 7.3.2 广度优先搜索 7.4 图的连通性问题 7.4.1 无向图的连通分量和生成树 7.4.2 有向图的强连通分量 7.4.3 最小生成树 7.4.4 关节点和重连通分量 7.5 有向无环图及其应用 7.5.1 拓扑排序 7.5.2 关键路径 7.6 最短路径 7.6.1 从某个源点到其余各顶点的最短路径 7.6.2 每一对顶点之间的最短路径 第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 无用单元收集 8.6 存储紧缩 第9章 查找 9.1 静态查找表 9.1.1 顺序表的查找 9.1.2 有序表的查找 9.1.3 静态树表的查找 9.1.4 索引顺序表的查找 9.2 动态查找表 9.2.1 二叉排序树和平衡二叉树 9.2.2 B树和B+树 9.2.3 键树 9.3 哈希表 9.3.1 什么是哈希表 9.3.2 哈希函数的构造方法 9.3.3 处理冲突的方法 9.3.4 哈希表的查找及其分析 第10章 内部排序 10.1 概述 10.2 插入排序 10.2.1 直接插入排序 10.2.2 其他插入排序 10.2.3 希尔排序 10.3 快速排序 10.4 选择排序 10.4.1 简单选择排序 10.4.2 树形选择排序 10.4.3 堆排序 10.5 归并排序 10.6 基数排序 10.6.1 多关键字的排序 10.6.2 链式基数排序 10.7 各种内部排序方法的比较讨论 第11章 外部排序 11.1 外存信息的存取 11.2 外部排序的方法 11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件(散列文件) 12.6 多关键字文件 12.6.1 多重表文件 12.6.2 倒排文件 附录A 名词索引 附录B 函数索引 参考书目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qx_java_1024

祝老板生意兴隆,财源广进!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值