☰
头歌顺序表1-6关通关指南:从插入删除到遍历的完整解析
2026/10/14 13:49:02 网站建设 项目流程

简介:本资源面向数据结构初学者与头歌平台刷题者,聚焦顺序表这一线性表基础结构,提供第1至6关的完整参考代码与实现思路。内容覆盖插入、删除、按序号查找值、按值查找序号、逆置以及两个有序顺序表合并六类核心操作,每关均配有可运行的C++源码,帮助读者理解数组存储、元素移动、边界判断与双指针合并等关键逻辑,适合课程实验、OJ练习与考前复习使用。资源包共1个docx文件,约54KB,文档内按关卡组织代码与注释,便于对照题目逐关调试与验证。目前已有16042人学习下载,说明该套解法在头歌顺序表练习中具有较高的参考价值与通过率。读者可借此快速掌握顺序表基本操作的代码框架、参数校验方式与常见易错点,为后续链表、栈、队列等数据结构学习打下扎实基础。

1. 头歌顺序表 1-6 关到底在练什么:别把它当成六道孤立的填空题

头歌数据结构顺序表的基本操作 1-6 关,本质是一条被拆成六段的线性表训练链:初始化、插入、删除、查找、取值、遍历,每一关只让你补全一个函数,但六关连起来才是一张完整的顺序表操作地图。很多人第一次做的时候,把每关当独立题目,复制粘贴过了就算完,结果期末复习或者考研 408 数据结构刷题时,发现自己连ListInsert的边界条件都写不利索。这篇笔记就按头歌顺序表这六关的实际顺序,把每关背后的原理、参数、易错点拆开讲清楚,让你不只是过关,而是真正能把顺序表的基本操作写进肌肉记忆里。适合正在做头歌数据结构实训、准备数据结构期末复习,或者刚开始刷王道数据结构线性表章节的人。

2. 顺序表的存储结构长什么样:先把地基打对

2.1 为什么顺序表用数组加长度两个字段就够了

顺序表的本质是一段连续内存加一个当前长度。头歌题目里通常给出的结构体定义是这样的:

#define MAXSIZE 100 typedef struct { int data[MAXSIZE]; // 静态分配,连续存储空间 int length; // 当前实际元素个数 } SqList;

data数组负责存元素,length负责记录当前存了多少个。这两个字段缺一不可:只有数组你不知道哪些位置是有效数据,只有长度你没有地方放数据。头歌顺序表 1-6 关的所有操作,本质上都是围绕这两个字段做文章——插入要让length加一,删除要让length减一,查找只读length不改它。

有些版本会用动态分配:

typedef struct { int *data; int length; int maxSize; } SeqList;

区别在于数组是malloc出来的,容量可以扩容。头歌基础关卡一般用静态版本,因为不用处理内存分配,能把注意力集中在逻辑本身。你拿到题目第一件事,就是看清楚结构体字段名到底是data还是elem,length还是len,字段名写错编译直接报错,这是最常见的翻车点。

2.2 位序和下标差 1 这件事,必须刻进条件反射

顺序表操作里最容易出错的地方,就是位序和数组下标的转换。人话讲:第 1 个元素存在data[0],第 i 个元素存在data[i-1]。头歌题目里说的“在第 i 个位置插入”,指的是位序 i,不是下标 i。

// 位序 i 对应下标 i-1 // 合法插入位序范围:1 <= i <= length+1 // 合法删除位序范围:1 <= i <= length

为什么插入的合法范围比删除多一个?因为插入可以在表尾之后追加,也就是插到第length+1个位置,相当于data[length] = e。而删除只能删已经存在的元素,所以最大位序是length。这个边界条件在头歌第 2 关和第 3 关里是必考的,写错一个等号,测试用例直接给你红色。

提示:每次写循环之前,先在草稿纸上画一个长度为 4 的顺序表,标出每个元素的下标和位序,再决定循环变量从哪开始、到哪结束。

3. 插入与删除:顺序表 1-6 关里最容易丢分的两关

3.1 第 2 关插入:从后往前搬,顺序不能反

插入操作的核心动作是腾位置。假设表里已经有 5 个元素,你要在第 3 个位置插入新元素,那么原来第 3、4、5 个元素都要往后挪一格,空出data[2]给新元素。

int ListInsert(SqList *L, int i, int e) { // 1. 判断插入位置是否合法 if (i < 1 || i > L->length + 1) { return 0; // 插入失败 } // 2. 判断表是否已满 if (L->length >= MAXSIZE) { return 0; } // 3. 从最后一个元素开始,依次后移 for (int j = L->length; j >= i; j--) { L->data[j] = L->data[j - 1]; } // 4. 放入新元素,长度加一 L->data[i - 1] = e; L->length++; return 1; }

循环for (int j = L->length; j >= i; j--)的意思是:把下标为j-1的元素搬到下标j。必须从后往前搬,如果从前往后搬,data[i]会被data[i-1]覆盖,后面的元素就全丢了。这个顺序问题是血泪经验,我见过太多人在这里翻车。

参数说明:L是顺序表指针,因为要修改length和data,必须传指针;i是位序,从 1 开始;e是待插入元素。返回值用 0 和 1 表示失败和成功,头歌题目通常要求返回bool或int,看题目函数签名。

3.2 第 3 关删除:从前往后搬,别把有效数据盖掉

删除是插入的逆操作。删掉第 i 个元素后,后面的元素要往前补位。

int ListDelete(SqList *L, int i, int *e) { // 1. 判断删除位置是否合法 if (i < 1 || i > L->length) { return 0; } // 2. 保存被删除的元素 *e = L->data[i - 1]; // 3. 从 i 位置开始,依次前移 for (int j = i; j < L->length; j++) { L->data[j - 1] = L->data[j]; } // 4. 长度减一 L->length--; return 1; }

这里循环从j = i开始,把data[j]搬到data[j-1],也就是从前往后搬。为什么删除要从前往后?因为你要用后面的元素覆盖前面的空位,如果从后往前搬,data[length-1]先被覆盖,再搬data[length-2]时用的还是旧值,逻辑就乱了。

e是指针,用来把删掉的元素带回去给调用者。头歌题目里这个参数有时候是int *e,有时候直接要求返回被删元素,看清楚函数签名再写。

3.3 插入和删除的时间复杂度到底怎么算

顺序表插入和删除的时间主要花在移动元素上。最好情况是插在表尾或删表尾,移动 0 次;最坏情况是插在表头或删表头,移动 n 次;平均情况移动 n/2 次。所以时间复杂度是 O(n)。

这个 O(n) 不是背出来的,是你数循环执行次数数出来的。头歌有些关卡会在测试用例里放一个长度为 100 的表,让你插在第一个位置,如果你的循环写错了方向,结果不对但程序不崩,这种错误最难查。建议每写完一个操作,自己手动模拟一遍长度为 3 的表,把每一步的data和length写出来。

4. 查找、取值与遍历:第 4 到第 6 关的送分题也有坑

4.1 第 4 关按值查找:找到了返回位序,没找到返回 0

按值查找就是从头到尾扫一遍,找到第一个等于目标值的元素,返回它的位序。

int LocateElem(SqList L, int e) { for (int i = 0; i < L.length; i++) { if (L.data[i] == e) { return i + 1; // 返回位序,不是下标 } } return 0; // 0 表示没找到 }

这里有两个细节:第一,返回值是i+1而不是i,因为题目要的是位序;第二,没找到返回 0,因为位序从 1 开始,0 可以安全地表示“不存在”。如果题目要求返回下标,那就返回i,没找到返回 -1。头歌不同关卡可能要求不一样,看题目注释。

参数L这里传的是值而不是指针,因为查找不修改表的内容。传值会复制整个结构体,如果表很大效率低,但头歌基础关卡表长最多 100,无所谓。实际工程里会传const SqList *L,既避免复制又防止误改。

4.2 第 5 关按位取值:先判合法再返回

按位取值就是给你一个位序 i,返回第 i 个元素的值。

int GetElem(SqList L, int i, int *e) { if (i < 1 || i > L.length) { return 0; // 位序不合法 } *e = L.data[i - 1]; return 1; }

这关的坑在于边界判断。如果i等于length,是合法的,因为最后一个元素的位序就是length。如果i等于length+1,不合法,因为那个位置还没有元素。很多人把i > L.length写成i >= L.length,导致最后一个元素取不到,测试用例直接挂。

4.3 第 6 关遍历:打印格式和分隔符要跟题目一致

遍历就是从头到尾输出所有元素。逻辑简单,但头歌的测试用例对输出格式很敏感。

void PrintList(SqList L) { for (int i = 0; i < L.length; i++) { if (i > 0) { printf(" "); // 元素之间用空格分隔 } printf("%d", L.data[i]); } printf("\n"); }

有的题目要求元素之间用空格,有的要求用逗号,有的要求最后一个元素后面不能有多余空格。这些格式细节不看清楚,逻辑全对也是 0 分。我的习惯是先把题目里的示例输出复制到记事本里,数一数分隔符到底是什么,再动手写。

注意:头歌的评测系统通常用 diff 比对输出,多一个空格少一个换行都会判错。写完先在本机跑一遍题目给的示例输入,确认输出一模一样再提交。

5. 头歌顺序表 1-6 关避坑排查:这五个错误我每个都犯过

5.1 现象:编译报错“unknown type name 'SqList'”

原因:结构体定义没包含进来,或者头文件里typedef写在了函数后面。头歌有些关卡把结构体定义放在了一个单独的.h文件里,你需要#include那个头文件,但题目没明说。

解决:先看题目给的代码框架里有没有#include "xxx.h",如果没有,把结构体定义复制到你的函数上面。如果已经有了,检查结构体名字拼写是否一致,SqList和SeqList差一个字母就是两个类型。

5.2 现象:插入操作测试用例通过,但删除操作结果全错

原因:插入和删除的循环方向搞反了。插入要从后往前搬,删除要从前往后搬。很多人写顺手了,两个都用同一个方向。

解决:记住一句话——插入是腾位置,从后往前;删除是补空位,从前往后。在草稿纸上画箭头,插入的箭头指向右,删除的箭头指向左。

5.3 现象:查找操作返回的位序总是比预期小 1

原因:返回了数组下标而不是位序。return i和return i+1差一个 1,但测试用例期望的是位序。

解决:看题目函数注释,如果写的是“返回其位置”或“返回位序”,就是i+1;如果写的是“返回下标”,才是i。头歌大多数关卡要位序。

5.4 现象:程序运行到一半崩溃,提示“segmentation fault”

原因:传参时该传指针的地方传了值,或者该传值的地方传了指针。比如ListInsert(SqList *L, ...)你传了一个SqList L,函数里L->length访问的就是非法地址。

解决:修改表内容的操作(插入、删除、初始化)传指针SqList *L,用L->length访问;只读操作(查找、取值、遍历)传值SqList L,用L.length访问。头歌函数签名已经给好了,照着抄参数类型就不会错。

5.5 现象:本地运行正确,提交到头歌判 0 分

原因:输出格式不对,或者函数返回值类型不匹配。头歌的评测机对格式极其严格。

解决:把题目里的示例输入输出复制出来,在本机用diff命令比对。另外检查函数返回类型,题目要求bool你返回int,有些编译器会隐式转换,但头歌可能直接判错。最稳妥的办法是照着题目给的函数签名一字不差地写。

6. 把六关串成一条线:用一个小项目验证你真的会了

六关做完之后,怎么确认自己不是“过关式记忆”?我的做法是写一个不超过 80 行的完整程序,把六个操作串起来,模拟一个简单的学生成绩管理:初始化空表,插入 5 个成绩,删除第 2 个,查找 90 分的位置,取出第 3 个成绩,最后打印全表。

#include <stdio.h> #define MAXSIZE 100 typedef struct { int data[MAXSIZE]; int length; } SqList; // 初始化 void InitList(SqList *L) { L->length = 0; } // 插入 int ListInsert(SqList *L, int i, int e) { if (i < 1 || i > L->length + 1 || L->length >= MAXSIZE) return 0; for (int j = L->length; j >= i; j--) { L->data[j] = L->data[j - 1]; } L->data[i - 1] = e; L->length++; return 1; } // 删除 int ListDelete(SqList *L, int i, int *e) { if (i < 1 || i > L->length) return 0; *e = L->data[i - 1]; for (int j = i; j < L->length; j++) { L->data[j - 1] = L->data[j]; } L->length--; return 1; } // 查找 int LocateElem(SqList L, int e) { for (int i = 0; i < L.length; i++) { if (L.data[i] == e) return i + 1; } return 0; } // 取值 int GetElem(SqList L, int i, int *e) { if (i < 1 || i > L.length) return 0; *e = L.data[i - 1]; return 1; } // 遍历 void PrintList(SqList L) { for (int i = 0; i < L.length; i++) { printf("%d ", L.data[i]); } printf("\n"); } int main() { SqList L; InitList(&L); int scores[] = {85, 92, 78, 90, 66}; for (int i = 0; i < 5; i++) { ListInsert(&L, i + 1, scores[i]); } printf("插入后: "); PrintList(L); int del; ListDelete(&L, 2, &del); printf("删除第2个(%d)后: ", del); PrintList(L); int pos = LocateElem(L, 90); printf("90分的位置: %d\n", pos); int val; GetElem(L, 3, &val); printf("第3个成绩: %d\n", val); return 0; }

这段代码把六关的操作全用上了。跑一遍,对照输出检查每一步的length和data变化。如果你能不看笔记默写出插入和删除的循环,并且能解释为什么插入从后往前、删除从前往后,那这六关才算真正吃透。

进阶一点的做法:把静态数组改成动态分配,加一个扩容函数,当length == maxSize时把容量翻倍。这个改动会逼你处理realloc和指针更新,是通往顺序表进阶用法的必经之路。我自己的习惯是每学完一个数据结构,就写一个带增删改查的小型管理程序,代码量控制在 100 行以内,跑通之后再回头看头歌的题目,会发现那些边界条件早就刻在脑子里了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询