吃透《数据结构》C 语言版:线性表的类型定义详解

作为数据结构的入门章节,线性表就像 “地基” 一样重要,而第二章 2.3 节的 “线性表的类型定义”,更是理解后续操作(插入、删除、查找等)的核心前提。今天就结合自己的学习笔记,用通俗的语言拆解这个知识点,帮大家避开 “死记硬背定义” 的坑。

一、先搞懂:为什么需要 “类型定义”?

刚开始学的时候我有个疑问:直接用数组存数据不就行了吗?为啥还要专门给线性表下 “类型定义”?后来才明白 ——类型定义是给线性表 “定规矩”

比如我们要存一个班级的学生成绩,“线性表” 是抽象概念,但具体到代码里,要明确:

  • 数据是什么(int 型成绩?还是包含姓名 + 成绩的结构体?)
  • 数据间的关系是什么(按学号顺序排列,前驱后继明确)
  • 能对这些数据做什么操作(比如添加成绩、删除不及格的、找最高分)

没有这个 “规矩”,后续写代码时很容易混乱(比如一会儿用数组存,一会儿用链表存,操作逻辑不统一)。而 “类型定义” 就是把这些规矩标准化,让线性表从 “模糊概念” 变成 “可落地的代码模板”。

二、核心:线性表的抽象数据类型(ADT)定义

教材里用 “抽象数据类型(ADT)” 来定义线性表,这部分是重点,但不用怕 “抽象” 两个字 —— 其实就是用 “伪代码” 描述线性表的 “三大要素”:数据对象、数据关系、基本操作。

1. 先看标准定义(教材核心内容)


ADT List {

数据对象:D = {a_i | a_i ∈ ElemSet, i=1,2,...,n, n≥0}

数据关系:R = {<a_{i-1}, a_i> | a_{i-1}, a_i ∈ D, i=2,...,n}

基本操作:

InitList(&L) // 初始化:构造一个空的线性表L

DestroyList(&L) // 销毁:释放线性表L的内存

ListEmpty(L) // 判断空:若L为空,返回TRUE,否则FALSE

ListLength(L) // 求长度:返回L中元素的个数

GetElem(L, i, &e) // 取元素:用e返回L中第i个元素的值

LocateElem(L, e, compare()) // 定位:返回e在L中第一个出现的位置

PriorElem(L, cur_e, &pre_e) // 求前驱:用pre_e返回cur_e的前驱

NextElem(L, cur_e, &next_e) // 求后继:用next_e返回cur_e的后继

ListInsert(&L, i, e) // 插入:在L的第i个位置插入元素e

ListDelete(&L, i, &e) // 删除:删除L的第i个元素,用e返回其值

TraverseList(L, visit()) // 遍历:对L中每个元素调用visit()操作

} ADT List

2. 逐句拆解,拒绝 “看不懂就跳过”

(1)数据对象:明确 “存什么”

D = {a_i | a_i ∈ ElemSet, i=1,2,...,n, n≥0}

  • 翻译:线性表里的每个元素(a₁、a₂…aₙ)都属于同一个 “元素集合”(ElemSet),比如都是 int 型、char 型,或者自定义的结构体(比如学生信息)。
  • 关键:n≥0——n 是元素个数,n=0 时就是 “空表”,这一点很重要(后续很多操作要先判断是否为空表)。
(2)数据关系:明确 “数据怎么排”

R = {<a_{i-1}, a_i> | a_{i-1}, a_i ∈ D, i=2,...,n}

  • 翻译:除了第一个元素(a₁),每个元素(aᵢ)都有且只有一个 “前驱”(aᵢ₋₁);除了最后一个元素(aₙ),每个元素都有且只有一个 “后继”(aᵢ₊₁)。
  • 举个例子:成绩表 [90, 85, 92],85 的前驱是 90,后继是 92—— 这就是线性表的 “线性关系”,也是它和树、图的核心区别。
(3)基本操作:明确 “能做什么”

这部分是后续代码实现的 “蓝图”,每个操作都要注意两个点:

  • 参数带不带 &(地址):带 & 表示 “要修改这个参数的值”(比如 InitList (&L) 要初始化 L,DestroyList (&L) 要释放 L 的内存);不带 & 表示 “只读取值,不修改”(比如 ListLength (L) 只返回长度)。
  • 操作的 “前置条件” 和 “后置条件”:比如 ListInsert (&L, i, e) 的前置条件是 “L 已存在,且 1≤i≤ListLength (L)+1”,后置条件是 “L 的长度加 1,第 i 个位置变成 e”—— 这些条件决定了代码里要加哪些判断(比如插入前要检查 i 的范围,否则会越界)。

三、从抽象到具体:C 语言中的线性表表示

ADT 是 “通用模板”,用 C 语言实现时,要结合 “存储结构”(后续会学顺序存储和链式存储)来定义。这里先讲最基础的 “顺序表”(用数组存储)的类型定义,帮大家建立 “抽象→具体” 的联系。

1. 第一步:定义 “元素类型”(ElemType)

教材里用ElemType表示元素的通用类型,实际用的时候要根据需求替换(比如存成绩就定义为 int,存字符串就定义为 char [])。


// 示例1:存储int型成绩

#define ElemType int

// 示例2:存储学生信息(姓名+成绩)

#define ElemType struct Student

struct Student {

char name[20];

int score;

};

  • 好处:后续代码里如果要改元素类型,只需要改这里,不用到处改代码(比如从 int 改成 float,只改 #define 那行)。

2. 第二步:定义 “顺序表” 的结构体

顺序表的核心是 “用数组存数据,加一个变量存长度”,所以结构体包含两部分:


#define MAXSIZE 100 // 线性表的最大容量(比如最多存100个成绩)

typedef struct {

ElemType data[MAXSIZE]; // 数组:存线性表的元素

int length; // 变量:存当前线性表的实际长度(初始为0)

} SqList; // SqList是“顺序表”的别名,后续可以用SqList定义变量

  • 注意:data[MAXSIZE]是 “静态数组”(容量固定),如果需要动态扩容,后续会学用 malloc 分配内存,但基础阶段先掌握静态定义。

四、学习小插曲:我踩过的两个坑

  1. 分不清 “ADT 定义” 和 “C 语言实现”:刚开始以为 ADT 里的InitList就是 C 语言函数,直接复制到代码里报错 —— 后来才明白,ADT 是 “伪代码描述操作”,C 语言实现时要写具体的函数体(比如 InitList 要把 length 设为 0)。
  1. 忽略 “操作的合法性判断”:比如写 ListDelete 时,没检查 i 的范围(比如 i=0 或 i>length),导致数组越界 —— 这就是没记住 ADT 里 “前置条件” 的后果,所以一定要把 “操作条件” 和代码逻辑绑定。

五、小结:这节内容要记住什么?

  1. 核心逻辑:线性表的类型定义 = 数据对象(存什么)+ 数据关系(怎么排)+ 基本操作(能做什么);
  1. C 语言关键:用ElemType统一元素类型,用结构体定义存储结构(比如顺序表的 SqList);
  1. 后续关联:这节的 “基本操作” 是后续插入、删除、查找的 “接口”,比如 ListInsert 的实现,必须基于 SqList 的结构体定义。

如果觉得抽象,建议先动手写一遍 “顺序表的初始化” 函数(比如 InitList (SqList &L),把 L.length 设为 0),再结合教材里的例子多练 —— 数据结构不是背定义,而是 “理解规矩,再用代码实现规矩”。

大家如果有没看懂的地方,或者有不同的理解,欢迎在评论区交流~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小辉编程充电站

技术路有你,打赏助我分享

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

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

打赏作者

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

抵扣说明:

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

余额充值