作為數據結構的入門章節,線性表就像 “地基” 一樣重要,而第二章 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 分配內存,但基礎階段先掌握靜態定義。
四、學習小插曲:我踩過的兩個坑
- 分不清 “ADT 定義” 和 “C 語言實現”:剛開始以為 ADT 里的InitList就是 C 語言函數,直接復制到代碼里報錯 —— 后來才明白,ADT 是 “偽代碼描述操作”,C 語言實現時要寫具體的函數體(比如 InitList 要把 length 設為 0)。
- 忽略 “操作的合法性判斷”:比如寫 ListDelete 時,沒檢查 i 的范圍(比如 i=0 或 i>length),導致數組越界 —— 這就是沒記住 ADT 里 “前置條件” 的后果,所以一定要把 “操作條件” 和代碼邏輯綁定。
五、小結:這節內容要記住什么?
- 核心邏輯:線性表的類型定義 = 數據對象(存什么)+ 數據關系(怎么排)+ 基本操作(能做什么);
- C 語言關鍵:用ElemType統一元素類型,用結構體定義存儲結構(比如順序表的 SqList);
- 后續關聯:這節的 “基本操作” 是后續插入、刪除、查找的 “接口”,比如 ListInsert 的實現,必須基于 SqList 的結構體定義。
如果覺得抽象,建議先動手寫一遍 “順序表的初始化” 函數(比如 InitList (SqList &L),把 L.length 設為 0),再結合教材里的例子多練 —— 數據結構不是背定義,而是 “理解規矩,再用代碼實現規矩”。
大家如果有沒看懂的地方,或者有不同的理解,歡迎在評論區交流~