


在编程学习中,“数据结构” 常常被初学者视为 “拦路虎”,尤其是听到 “顺序表”“链表” 这类专业术语时,很容易陷入 “抽象难懂” 的误区。但其实数据结构的本质,就是为了更高效地管理和操作数据 —— 就像我们日常生活中整理物品的方式一样。今天我们就从最基础的 “顺序表” 开始,用生活化的例子 + 简单代码,把抽象概念变成你能摸得着的 “实用工具”。
其实顺序表一点都不复杂,你可以把它理解成 “一个有规则的抽屉柜”—— 每个抽屉大小一样,按顺序排列,里面只能放同一种类型的东西(比如全放书本,或者全放文具),而且你能通过抽屉的 “位置编号” 快速找到对应的物品。
如果你学过 C 语言或其他编程语言,一定对 “数组” 不陌生。比如int arr[10]就是一个能存 10 个整数的数组,每个元素有固定的下标(从 0 开始),我们可以通过arr[0]“精准定位” 第一个元素。
而顺序表,其实就是在数组的基础上,增加了 “长度管理” 的功能。打个比方:数组就像一个 “没有标签的空抽屉柜”,你知道它有 10 个抽屉,但不知道里面装了多少东西、有没有空抽屉;而顺序表就像给这个抽屉柜贴了一张 “清单”,上面写着 “当前装了 3 件物品”,这样你就不会往已经装满的抽屉里硬塞东西,也不会找不到空抽屉的位置。
用代码来表示的话,顺序表的核心结构是这样的(对应你提供的代码片段):
// 定义顺序表的结构
typedef struct{
elemtype data[maxsize]; // 底层就是一个数组,用来存数据(相当于抽屉柜)
int length; // 记录当前有多少个元素(相当于清单上的“已装数量”)
}seqlist;
这里的elemtype是 “元素类型” 的缩写(代码里用typedef把int重命名为elemtype),意思是这个顺序表可以存整数;maxsize是数组的最大容量(比如 100),代表抽屉柜的 “总抽屉数”;length则是当前实际存了多少个元素,比如你存了 3 个数字,length就是 3。
可能有人会问:既然顺序表底层是数组,那直接用数组不就行了?其实顺序表的价值,就在于解决了数组的 “痛点”——
比如你用数组存数据时,想知道 “现在装了多少个元素”,只能自己遍历数组统计(如果有未赋值的元素,还会统计错);想在指定位置插入一个元素,得自己手动把后面的元素 “往后挪”,还得担心数组会不会越界。
而顺序表把这些 “麻烦事” 都封装成了现成的功能(比如初始化、插入、删除、查找),就像给你提供了一套 “抽屉柜操作指南”,你不用再自己琢磨 “怎么挪抽屉”“怎么记数量”,直接用就行。
我们结合你提供的 C 语言代码,一步步拆解顺序表的核心操作 —— 就像教你怎么 “用抽屉柜存东西、取东西、挪东西”。
初始化的目的,是让顺序表从 “未使用状态” 变成 “可以使用的空状态”。就像你刚买了一个新抽屉柜,第一步要做的不是直接放东西,而是先在清单上写 “当前已装数量:0”,告诉自己 “现在所有抽屉都是空的”。
对应代码:
// 初始化顺序表:把length设为0,代表当前没有元素
void initdata(seqlist *L){
L->length=0; // L是指向顺序表的指针,通过“->”修改length的值
}
使用的时候,你需要先 “创建一个抽屉柜”(定义顺序表变量),再 “贴清单”(调用初始化函数):
seqlist list; // 定义一个顺序表变量(相当于买了一个抽屉柜)
initdata (&list); // 初始化:把list的length设为0(贴空清单)
往顺序表末尾加元素,是最常用的操作之一,就像你往抽屉柜的 “最后一个空抽屉” 里放东西 —— 先看一下清单,知道当前装了 3 件(length=3),就把新东西放进第 4 个抽屉(数组下标是 3,因为数组从 0 开始),然后把清单上的 “已装数量” 改成 4。
对应代码:
// 在顺序表尾端添加元素e(e就是要放的“东西”)
int addseqlist(seqlist*L,elemtype e){
// 先判断:抽屉柜是不是满了?(length >= 最大容量maxsize)
if((L->length)>=maxsize){
printf("顺序表已满");
return 0; // 满了就返回0,代表添加失败
}
// 没满的话,把e放进“最后一个空位置”(下标=length)
L->data[L->length]=e;
L->length++; // 清单数量+1
return 1; // 返回1,代表添加成功
}
比如代码里添加了 4 个数字:8、88、888、8848,就像往抽屉柜里依次放了 4 件东西,此时length变成 4,数组里的数据就是[8,88,888,8848]。
生活中我们常会遇到 “中间插东西” 的场景:比如抽屉柜里已经放了 4 本书(位置 1 到 4),现在想把一本新书插进 “位置 2”,就需要先把位置 2、3、4 的书往后挪一个抽屉,腾出位置再放新书。顺序表的插入操作也是一样的逻辑。
对应代码:
// 在pos位置插入元素e(pos是用户输入的“要插的位置”)
int insertlist(seqlist*L,int pos,elemtype e){
// 先判断:插入的位置是不是合理(比如不能比当前元素数量还大)
if(pos<L->length){
// 把pos位置及后面的元素“往后挪一位”(从最后一个元素开始挪,避免覆盖)
for(int i=L->length;i>=pos;i--){
L->data[i+1]=L->data[i];
}
// 腾出位置后,把e放进pos-1的下标(因为数组从0开始,位置1对应下标0)
L->data[pos-1]=e;
L->length++; // 数量+1
}
}
比如你想在 “位置 2” 插入数字 10,原来的数组是[8,88,888,8848],执行插入后会变成[8,10,88,888,8848]—— 就像把第 2 个抽屉的书往后挪,再把新书放进去。
删除操作和插入操作是 “反过来” 的:比如你想把抽屉柜 “位置 3” 的东西拿走,就需要先把位置 3 的东西取出来,再把位置 4、5 的东西往前挪一个抽屉,填补空出来的位置,最后把清单上的 “已装数量” 减 1。
对应代码:
// 删除del位置的元素,并用e指针返回被删除的元素(方便用户查看删了什么)
int deleteelem(seqlist*L,int del,elemtype *e){
// 先把要删除的元素存起来(比如要删位置3的元素,就先把data[2]存到*e里)
*e=L->data[del-1];
// 判断删除位置是否合理
if(del<L->length){
// 把del位置后面的元素“往前挪一位”(从del位置开始挪)
for(int i=del;i< L->length;i++){
L->data[i-1]=L->data[i];
}
}
L->length--; // 数量-1
return 1; // 删除成功
}
比如你删除 “位置 2” 的元素,数组[8,10,88,888,8848]会变成[8,88,888,8848],同时会返回被删除的元素 10—— 就像把第 2 个抽屉的东西拿走,后面的书往前补位。
查找操作就像 “找东西”:比如你想找抽屉柜里有没有 “88” 这本书,就从第一个抽屉开始逐个翻看,直到找到对应的抽屉位置;如果没找到,就告诉自己 “没有这本书”。
对应代码:
// 查找元素e,返回它的位置(从1开始数,方便用户理解)
int findelem(seqlist*L,elemtype e){
// 遍历顺序表,逐个对比元素
for(int i=0;i<L->length;i++){
if(L->data[i]==e){
return i+1; // 找到就返回位置(下标i+1,因为用户习惯从1开始数)
}
}
}
比如你查找 “88”,代码会遍历数组,发现data[1]是 88,就返回位置 2—— 告诉你 “88 在第 2 个抽屉里”。
学完了操作,我们还要知道顺序表的 “适用场景”—— 就像你不会用抽屉柜装冰箱,也不会用行李箱装书本,每个工具都有自己的优缺点。
因为顺序表底层是数组,元素的存储位置是连续的,而且每个元素的大小固定,所以我们可以通过 “公式” 快速计算出任意元素的位置(比如第 i 个元素的下标是 i-1)。这种 “随机访问” 的能力是顺序表最大的优势 —— 就像你知道抽屉柜第 3 个抽屉的位置,不用翻前面的抽屉,直接就能打开。
比如你想访问顺序表的第 5 个元素,直接用L->data[4]就能拿到,时间复杂度是 O (1)(瞬间完成)。
如果要在顺序表的中间插入或删除元素,就需要挪动后面所有的元素 —— 比如你在有 1000 个元素的顺序表中,往第 2 个位置插入元素,就需要挪动 999 个元素,效率很低(时间复杂度是 O (n))。
这就像你在一个装满书的抽屉柜中间插一本书,需要把后面所有的书都往后挪,书越多,挪起来越费时间。
看到这里,你应该能明白:顺序表其实就是给数组加了一个 “长度清单”,让我们能更方便地管理数据。它的逻辑在生活中随处可见 ——
数据结构从来不是 “脱离生活的抽象概念”,而是把生活中的 “整理逻辑” 用代码实现出来。下次再遇到 “顺序表”,不妨想想你家的抽屉柜 —— 你就会发现,原来它这么简单!
如果你想进一步实践,可以试着修改文中的代码:比如把elemtype改成char,让顺序表存字符;或者增加 “修改元素” 的功能,给你的 “抽屉柜” 再添一个新技能~