资讯动态

数据结构实验二:链表实现与应用详解,从指针操作到多项式加法

发布时间:2026/10/19 13:07:18 来源:尧图企业网站定制
1. 链表实验到底在练什么先搞清楚这个再动手写代码“数据结构实验课实验二、链表的实现及应用”这个标题放在课程表里可能只是短短一行字但真正动手写过的人都知道这是整个数据结构课程里第一个能让你把指针、内存、结构体和逻辑思维全部串起来的大关卡。很多同学在实验一写顺序表的时候感觉还行数组嘛下标一加一减就完事了但到了链表这里画风突变——指针指来指去删一个节点要考虑前驱和后继插入位置不同代码完全不一样调试的时候程序直接崩溃或者打印出一堆乱码这些都是常态。这篇文章就是写给正在做这个实验、或者准备复习链表操作的人看的。不管你是刚学C语言、指针还不太熟的低年级学生还是已经工作但想回头把基础打牢的开发者我都会从实验的整体设计思路开始把单链表的创建、插入、删除、查找、遍历、销毁这几个核心操作逐一拆开讲再延伸到链表的几个典型应用场景比如多项式加法、约瑟夫环、学生成绩管理这类课程设计里常见的题目。中间会穿插大量我在实际写代码和帮别人调试时踩过的坑以及那些教科书上不会专门写、但你不注意就一定会出问题的地方。链表这个结构本身并不复杂它的核心思想就一句话用指针把一块块不连续的内存串起来。但为什么很多人学完链表还是不会用因为链表操作对边界条件极其敏感头节点、尾节点、空链表、只有一个节点的链表这些情况处理方式各不相同而教科书上的代码往往只展示最标准的情况一到实际写就各种越界。所以这篇文章的重点不是把书上的代码抄一遍而是把每个操作背后的逻辑讲透让你自己能推导出代码而不是背代码。2. 实验整体设计与思路拆解2.1 为什么实验课要用链表而不是数组顺序表和链表是线性表的两种基本实现方式实验一通常写顺序表实验二写链表这个安排是有意为之的。顺序表的核心优势是随机访问你想取第5个元素直接下标4就拿到了时间复杂度O(1)。但它有两个硬伤一是插入和删除平均要移动一半的元素时间复杂度O(n)二是容量固定扩容需要重新申请一块更大的连续内存再把数据搬过去。链表恰好在这两点上互补。插入和删除只需要改几个指针不需要搬数据时间复杂度O(1)前提是你已经找到了位置。容量也不需要预先确定来一个节点就申请一块内存理论上只要系统内存够链表可以一直延伸下去。代价是你不能直接跳到第n个节点必须从头一个一个往后找查找的时间复杂度是O(n)。实验课安排链表实验核心目的不是让你比较两种结构谁更好而是让你理解“用指针组织数据”这种思维方式。数组是连续内存的抽象链表是离散内存的抽象后者更接近计算机实际管理内存的方式。你后面学的树、图本质上都是链式结构的变体链表是理解所有这些结构的基础。2.2 带头节点还是不带头节点一个必须提前决定的问题写链表代码之前你必须先做一个决定链表要不要带头节点。所谓头节点就是一个不存实际数据、只用来简化操作的额外节点。这个问题看起来小但它直接影响你后面所有操作的代码写法。不带头节点的链表头指针直接指向第一个实际数据节点。这种写法的问题在于插入和删除第一个节点时你需要修改头指针本身而修改头指针意味着你要把指针的指针传进函数或者用返回值重新赋值。很多同学在这里被二级指针绕晕就是因为选了不带头节点的方案。带头节点的链表头指针指向一个哑节点第一个实际数据存在头节点的next里。这样一来无论你插入还是删除的是第一个位置还是中间位置操作逻辑完全一致因为你永远不需要修改头指针本身只需要修改某个节点的next指针。代价是多占一个节点的内存以及初始化的时候要多申请一次。我的建议很明确实验课写链表一律带头节点。多花几行代码初始化换来后面所有操作逻辑统一这笔账怎么算都划算。教科书上两种写法都会出现但实际工程代码里带头节点的写法占绝对主流你早点习惯没有坏处。2.3 模块划分把链表操作拆成独立函数实验课通常要求你把链表操作封装成函数而不是全部写在main里。这个要求不是为了好看而是为了让你理解接口和实现分离的思想。一个标准的链表模块至少应该包含以下函数初始化链表创建一个头节点返回头指针销毁链表释放所有节点的内存清空链表只保留头节点释放数据节点判空头节点的next是否为NULL求长度遍历计数按位置插入在第i个位置前插入新元素按位置删除删除第i个位置的元素按值查找返回第一个匹配节点的位置或指针遍历打印输出所有元素头插法建表从空链表开始每次在头部插入尾插法建表从空链表开始每次在尾部插入这些函数的命名和参数设计没有绝对标准但有一个原则函数的功能要单一一个函数只做一件事。比如插入和建表要分开建表可以调用插入但插入不应该依赖建表。这样你调试的时候可以单独测试每个函数出了问题也容易定位。3. 核心细节解析与实操要点3.1 节点结构体定义看似简单坑在细节节点结构体的定义看起来是最简单的一步但有几个细节不注意就会出问题。先看标准写法typedef int ElemType; typedef struct LNode { ElemType data; struct LNode *next; } LNode, *LinkList;这里有几个点值得展开说。第一typedef给struct LNode起了两个别名LNode表示节点本身LinkList表示指向节点的指针。这样后面声明变量的时候LinkList L表示一个链表头指针LNode *p表示一个节点指针语义上更清晰。当然你也可以不用typedef直接写struct LNode *但代码会啰嗦很多。第二data的类型用ElemType而不是直接写int这是为了后续扩展。实验课通常存整数但如果你要做学生成绩管理data可能是一个结构体。用ElemType做一层抽象换数据类型的时候只需要改typedef那一行。第三next指针的类型是struct LNode *不能写成LNode *。因为在结构体定义内部LNode这个别名还没有生效编译器还不认识它。这是一个非常经典的编译错误很多人第一次写链表都会在这里卡住。注意如果你在结构体里嵌套了其他结构体要确保被嵌套的结构体已经完整定义否则会出现“不完整类型”的编译错误。3.2 内存申请与释放malloc和free的配对使用链表节点是动态申请的用mallocC语言或newC。每次malloc都必须对应一次free否则就是内存泄漏。实验课的代码通常运行时间短内存泄漏可能看不出来但这是一个必须从第一天就养成的习惯。申请一个节点的标准写法LNode *p (LNode *)malloc(sizeof(LNode)); if (p NULL) { printf(内存申请失败\n); exit(1); } p-data value; p-next NULL;这里malloc前面的强制类型转换在C语言里不是必须的但加上更清晰。sizeof(LNode)不要写成sizeof(LNode *)前者是节点大小后者是指针大小写错了申请的内存不够后面赋值就会越界。释放节点的写法free(p); p NULL;free之后把指针置空是一个好习惯防止后面误用已经释放的指针。虽然实验课代码通常不会在free之后再访问但养成这个习惯对以后写大程序有好处。提示如果你在删除节点时先free了节点然后又试图访问它的next程序会崩溃或者读到垃圾数据。正确的顺序是先把next保存下来再free当前节点。3.3 头插法与尾插法两种建表方式的本质区别头插法和尾插法是链表建表的两种基本方式它们的区别不仅仅是插入位置不同最终得到的链表顺序也完全相反。头插法的逻辑是每次新节点都插在头节点后面成为新的第一个数据节点。代码大致如下void CreateListHead(LinkList L, int n) { for (int i 0; i n; i) { LNode *p (LNode *)malloc(sizeof(LNode)); p-data i 1; p-next L-next; L-next p; } }如果你依次插入1、2、3、4、5最终链表里的顺序是5、4、3、2、1。因为每次新节点都挤到最前面最早插入的反而被推到了最后。尾插法需要维护一个尾指针每次新节点接在尾部void CreateListTail(LinkList L, int n) { LNode *r L; for (int i 0; i n; i) { LNode *p (LNode *)malloc(sizeof(LNode)); p-data i 1; p-next NULL; r-next p; r p; } }尾插法得到的链表顺序和插入顺序一致。两种方法没有优劣之分取决于你的需求。头插法代码更简洁不需要维护尾指针尾插法更符合直觉但多一个指针变量。实操心得很多同学在尾插法里忘记更新尾指针r导致所有新节点都接在同一个位置最后链表里只有一个节点。检查代码的时候重点看r p这一行有没有漏。3.4 按位置插入指针操作的顺序决定成败按位置插入是链表操作里最容易出错的一个因为涉及两个指针的修改顺序错了就会断链或者形成环。假设我们要在第i个位置前插入一个新节点标准步骤如下从头节点开始向后移动i-1次找到第i-1个节点记为p如果p为空或者i小于1说明位置非法返回错误申请新节点s赋值让s的next指向p的next让p的next指向s关键在第4步和第5步的顺序。如果你先执行p-next s那么原来p后面的节点就找不到了因为p的next已经被改成s了。正确的做法是先让新节点接管后面的链表再让前驱节点指向新节点。用生活类比来说就像在一根铁链中间加一个环。你必须先把新环扣在后面那截链子上再把前面那截链子扣到新环上。反过来操作的话后面那截链子就掉地上找不到了。int ListInsert(LinkList L, int i, ElemType e) { LNode *p L; int j 0; while (p ! NULL j i - 1) { p p-next; j; } if (p NULL || j i - 1) return 0; LNode *s (LNode *)malloc(sizeof(LNode)); s-data e; s-next p-next; p-next s; return 1; }注意循环条件里的j i - 1和p ! NULL的顺序不能反。如果p已经是NULL你还去访问p-next程序直接崩溃。短路求值在这里救了你一命但前提是条件顺序写对。3.5 按位置删除别忘了释放内存删除操作和插入操作是对称的但多了一步释放内存。标准步骤如下找到第i-1个节点记为p检查p和p-next是否为空让q指向p-nextq是要删除的节点让p-next指向q-next释放qint ListDelete(LinkList L, int i, ElemType *e) { LNode *p L; int j 0; while (p ! NULL j i - 1) { p p-next; j; } if (p NULL || p-next NULL) return 0; LNode *q p-next; *e q-data; p-next q-next; free(q); return 1; }这里把被删除节点的数据通过指针参数返回是为了让调用者知道删了什么。如果你不需要这个信息可以省略。常见错误删除节点后忘记free或者free之后还去访问q-data。前者造成内存泄漏后者造成未定义行为。实验课代码量小可能不会立刻出问题但这是坏习惯。4. 实操过程与核心环节实现4.1 完整代码框架从零搭建一个可运行的链表程序把前面的碎片拼起来一个完整的链表实验程序应该包含以下部分。我以学生成绩管理为场景每个节点存一个学号和成绩这样比单纯存整数更有实际意义。#include stdio.h #include stdlib.h typedef struct { int id; float score; } Student; typedef struct LNode { Student data; struct LNode *next; } LNode, *LinkList; LinkList InitList() { LinkList L (LinkList)malloc(sizeof(LNode)); if (L NULL) { printf(初始化失败\n); exit(1); } L-next NULL; return L; } void DestroyList(LinkList L) { LNode *p L-next; while (p ! NULL) { LNode *q p-next; free(p); p q; } free(L); } int ListLength(LinkList L) { int len 0; LNode *p L-next; while (p ! NULL) { len; p p-next; } return len; } void PrintList(LinkList L) { LNode *p L-next; printf(学号\t成绩\n); while (p ! NULL) { printf(%d\t%.1f\n, p-data.id, p-data.score); p p-next; } printf(\n); } int ListInsert(LinkList L, int i, Student e) { LNode *p L; int j 0; while (p ! NULL j i - 1) { p p-next; j; } if (p NULL || j i - 1) return 0; LNode *s (LNode *)malloc(sizeof(LNode)); s-data e; s-next p-next; p-next s; return 1; } int ListDelete(LinkList L, int i, Student *e) { LNode *p L; int j 0; while (p ! NULL j i - 1) { p p-next; j; } if (p NULL || p-next NULL) return 0; LNode *q p-next; *e q-data; p-next q-next; free(q); return 1; } LNode *LocateElem(LinkList L, int id) { LNode *p L-next; while (p ! NULL p-data.id ! id) { p p-next; } return p; } int main() { LinkList L InitList(); Student s1 {1001, 85.5}; Student s2 {1002, 92.0}; Student s3 {1003, 78.5}; ListInsert(L, 1, s1); ListInsert(L, 2, s2); ListInsert(L, 3, s3); printf(插入后链表长度%d\n, ListLength(L)); PrintList(L); Student del; ListDelete(L, 2, del); printf(删除的学号%d\n, del.id); PrintList(L); LNode *found LocateElem(L, 1003); if (found ! NULL) { printf(找到学号1003成绩%.1f\n, found-data.score); } DestroyList(L); return 0; }这个框架可以直接编译运行你只需要根据实验要求调整data的类型和操作的细节。建议你先把这段代码跑通然后在此基础上修改而不是从零开始写。从零开始写很容易在指针操作上卡住先有一个能跑的版本再逐步替换成自己的逻辑效率会高很多。4.2 多项式加法链表应用的经典案例多项式加法是链表实验里最常见的应用题之一。每个节点存一个系数和一个指数链表按指数递减排列。两个多项式相加本质上就是合并两个有序链表指数相同的项系数相加指数不同的项按顺序插入。节点定义typedef struct PNode { float coef; int exp; struct PNode *next; } PNode, *Polynomial;加法逻辑Polynomial AddPoly(Polynomial A, Polynomial B) { Polynomial C (Polynomial)malloc(sizeof(PNode)); C-next NULL; PNode *pa A-next; PNode *pb B-next; PNode *pc C; while (pa ! NULL pb ! NULL) { if (pa-exp pb-exp) { PNode *s (PNode *)malloc(sizeof(PNode)); s-coef pa-coef; s-exp pa-exp; s-next NULL; pc-next s; pc s; pa pa-next; } else if (pa-exp pb-exp) { PNode *s (PNode *)malloc(sizeof(PNode)); s-coef pb-coef; s-exp pb-exp; s-next NULL; pc-next s; pc s; pb pb-next; } else { float sum pa-coef pb-coef; if (sum ! 0) { PNode *s (PNode *)malloc(sizeof(PNode)); s-coef sum; s-exp pa-exp; s-next NULL; pc-next s; pc s; } pa pa-next; pb pb-next; } } while (pa ! NULL) { PNode *s (PNode *)malloc(sizeof(PNode)); s-coef pa-coef; s-exp pa-exp; s-next NULL; pc-next s; pc s; pa pa-next; } while (pb ! NULL) { PNode *s (PNode *)malloc(sizeof(PNode)); s-coef pb-coef; s-exp pb-exp; s-next NULL; pc-next s; pc s; pb pb-next; } return C; }这个算法的核心是归并排序的合并步骤两个链表各取一个节点比较指数小的先接入结果链表相等则系数相加。注意系数相加为0的项要跳过不生成节点。实操心得多项式加法最容易出错的地方是忘记处理剩余节点。当其中一个链表遍历完后另一个链表剩下的节点要全部接到结果链表后面。很多同学只写了while循环里的逻辑忘了后面的两个while导致结果不完整。4.3 约瑟夫环循环链表的典型应用约瑟夫环问题是这样的n个人围成一圈从第一个人开始报数报到m的人出列然后从下一个人继续报数直到所有人出列。这个问题用循环链表解决非常自然因为循环链表天然就是首尾相连的。构建循环链表typedef struct JNode { int id; struct JNode *next; } JNode, *JList; JList CreateJosephus(int n) { JList head (JList)malloc(sizeof(JNode)); head-id 1; head-next head; JNode *tail head; for (int i 2; i n; i) { JNode *p (JNode *)malloc(sizeof(JNode)); p-id i; p-next head; tail-next p; tail p; } return head; }出列逻辑void Josephus(JList head, int m) { JNode *p head; JNode *prev NULL; while (p-next ! p) { for (int i 1; i m; i) { prev p; p p-next; } printf(%d出列\n, p-id); prev-next p-next; free(p); p prev-next; } printf(最后剩下%d\n, p-id); free(p); }这里的关键是维护一个prev指针因为删除节点需要知道前驱。循环链表的遍历终止条件是p-next ! p当只剩一个节点时它的next指向自己。注意约瑟夫环的报数是从1开始还是从0开始出列后从下一个人重新报数还是继续报数这些细节不同题目可能有不同要求。写代码之前先把规则确认清楚否则逻辑对了但结果不对。4.4 链表反转面试常考实验课也常出现链表反转是一个看起来简单但写起来容易绕晕的操作。核心思路是遍历链表每次把当前节点的next指向前一个节点。需要三个指针prev、curr、next。LinkList ReverseList(LinkList L) { LNode *prev NULL; LNode *curr L-next; while (curr ! NULL) { LNode *next curr-next; curr-next prev; prev curr; curr next; } LNode *newHead (LNode *)malloc(sizeof(LNode)); newHead-next prev; return newHead; }如果你不带头节点反转后头指针直接指向原来的尾节点。带头节点的写法需要重新创建一个头节点或者直接修改原头节点的next。实操心得反转链表的时候一定要先把curr-next保存到next变量再修改curr-next。如果你先修改了curr-next后面的链表就找不到了。这个顺序问题和插入操作是同一个道理。5. 常见问题与排查技巧实录5.1 程序崩溃的几种典型原因链表实验里程序崩溃几乎人人都会遇到崩溃的原因无非以下几种崩溃现象可能原因排查方法段错误Segmentation fault访问了NULL指针的成员在每次解引用前检查指针是否为NULL打印乱码使用了未初始化的指针检查malloc后是否赋值free后是否置空死循环链表形成了环检查插入删除时指针修改顺序结果不对但不崩溃逻辑错误用少量数据手动模拟打印中间状态free报错重复释放或释放了非malloc的指针检查每个free是否对应一个malloc段错误是最常见的通常是因为你试图访问一个空指针的next或data。比如遍历链表时循环条件写成了while (p-next ! NULL)而不是while (p ! NULL)当p已经是NULL时还去访问p-next直接崩溃。5.2 指针丢失与断链的预防指针丢失是链表操作中最隐蔽的错误程序可能不崩溃但链表结构已经被破坏。预防的方法很简单每次修改指针之前先想清楚这个指针原来指向哪里修改后还能不能找到后面的节点。一个实用的技巧是画图。在纸上画出链表的节点和指针然后一步一步模拟你的代码看指针怎么变。这个方法看起来很笨但对于理解链表操作非常有效。我见过很多同学盯着代码看半天看不出问题一画图立刻就明白了。提示如果你在调试时发现链表打印出来的节点数量不对或者顺序不对大概率是某个地方的指针修改顺序错了。重点检查插入和删除操作。5.3 内存泄漏的检测与避免实验课的代码通常运行时间短内存泄漏不会导致程序崩溃但这是一个必须避免的坏习惯。检测内存泄漏最简单的方法是在每个malloc和free的地方加打印语句看它们是否配对。LNode *p (LNode *)malloc(sizeof(LNode)); printf(malloc: %p\n, p); // ... printf(free: %p\n, p); free(p);如果malloc的次数和free的次数不一致就说明有泄漏。在Linux下可以用valgrind工具检测Windows下可以用Visual Studio的内存检测功能。实验课不要求这么严格但你自己写代码的时候应该有这个意识。5.4 边界条件检查清单链表操作的边界条件特别多写代码的时候容易漏。我整理了一个检查清单每次写完一个操作函数对照这个清单过一遍空链表头节点的next是NULL插入第一个元素和删除最后一个元素是否正常只有一个节点删除这个节点后链表是否为空插入时前驱和后继是否正确在头部插入新节点是否成为第一个数据节点在尾部插入尾节点的next是否置为NULL在头部删除头节点的next是否更新在尾部删除新的尾节点next是否置为NULL位置参数非法i小于1或大于长度加1时是否返回错误查找不存在的值是否返回NULL而不是崩溃这个清单看起来繁琐但能帮你避免90%的边界错误。我自己的习惯是每写一个函数先用空链表、单节点链表、双节点链表各测一遍确认没问题再测正常情况。5.5 调试技巧打印中间状态链表调试最有效的方法是在关键位置打印链表状态。不要等到最后才打印结果而是在每次插入、删除之后都打印一次观察链表的变化是否符合预期。void DebugPrint(LinkList L, const char *msg) { printf( %s \n, msg); LNode *p L-next; while (p ! NULL) { printf(%d - , p-data); p p-next; } printf(NULL\n); }在插入前调用DebugPrint(L, 插入前)插入后调用DebugPrint(L, 插入后)对比两次输出就能看出插入操作是否正确。这个方法虽然原始但非常有效尤其是对于指针操作还不熟练的同学。6. 从实验到实战链表还能怎么用6.1 链表的变体双向链表与循环链表单链表只能从头往后遍历如果你需要频繁地向前查找单链表就不够用了。双向链表每个节点多一个prev指针可以双向遍历。循环链表尾节点的next指向头节点形成一个环。这两种变体在实验课里可能只要求了解但在实际应用中非常常见。双向链表的插入和删除比单链表多一个指针要维护但逻辑是对称的。循环链表在约瑟夫环、轮询调度等场景下很自然。如果你把单链表吃透了这两种变体花半个小时就能上手。6.2 链表在操作系统和数据库中的应用链表在实际系统里无处不在。操作系统里的进程调度队列、内存空闲块管理、文件系统的目录项很多都是用链表或链表的变体实现的。数据库的索引结构B树叶子节点之间也是用链表串起来的方便范围查询。你可能会觉得实验课写的链表太简单跟这些实际系统差得远。但核心思想是一样的用指针把离散的数据块组织起来通过修改指针来动态调整结构。实验课的目的是让你把这个核心思想练熟而不是让你直接去写操作系统。6.3 链表与数组的选择什么时候用哪个链表和数组没有绝对的优劣选择取决于你的使用场景。如果你需要频繁随机访问数组更合适如果你需要频繁插入删除链表更合适。如果你既需要随机访问又需要频繁插入删除可以考虑跳表、平衡树等更复杂的数据结构。实验课让你分别用数组和链表实现线性表目的就是让你亲身体会这两种结构的差异。你写完之后应该能回答一个问题如果我要实现一个通讯录支持按姓名查找、按姓名排序、频繁添加删除联系人我应该用数组还是链表答案是看哪个操作更频繁。如果查找远多于增删数组加二分查找可能更好如果增删远多于查找链表更合适。实操心得实验报告里通常会要求你分析两种结构的时间复杂度。不要只写O(n)和O(1)这种结论要结合你的实际测试数据来说明。比如插入1000个元素数组用了多少时间链表用了多少时间这样更有说服力。6.4 链表练习的进阶方向如果你已经把基本的单链表操作练熟了可以尝试以下几个进阶练习用链表实现大整数加法每个节点存一位数字模拟手工加法的进位用链表实现LRU缓存最近使用的节点移到头部淘汰尾部的节点合并两个有序链表类似多项式加法但不合并相同项判断链表是否有环快慢指针法一个走一步一个走两步找到链表的中间节点快慢指针法快指针走到尾时慢指针正好在中间这些题目在面试里经常出现也是检验你是否真正理解链表的好方法。实验课可能不要求做这些但你自己花时间练一练对后面的学习和面试都有帮助。7. 实验报告怎么写才不会被扣分7.1 实验目的和原理部分实验报告的开头通常要求写实验目的和原理。这部分不要照抄教科书用自己的话把链表的定义、特点、与数组的区别说清楚。实验目的要具体比如“掌握单链表的创建、插入、删除、查找操作”比“学习链表”要好得多。原理部分可以画一个简单的链表结构图标注头节点、数据节点、尾节点、next指针。图不用很漂亮手画拍照贴上去也行关键是让读者一眼看懂链表的结构。7.2 代码和测试结果部分代码部分不要只贴一个完整的main函数要按功能模块分段贴每个函数配一段说明。测试结果要包含正常情况和边界情况比如空链表、单节点链表、在头部插入、在尾部删除等。每种情况贴出输入和输出证明你的代码是正确的。如果测试过程中发现了bug并修复了把bug的现象和修复方法也写进去。这比只写一个完美结果更有价值老师也能看出你真的动手调试了。7.3 心得体会部分心得体会是实验报告里最容易拿分也最容易丢分的部分。不要写“通过这次实验我学到了很多”这种空话要写具体的收获和踩过的坑。比如“我在写插入操作时一开始把指针修改顺序搞反了导致链表断链后来画图分析才找到问题”这种具体的经历比泛泛而谈好得多。你也可以写一些对链表应用的思考比如“我发现链表在频繁插入删除的场景下比数组更有优势但查找效率低实际使用时需要根据场景选择”。这种思考能体现你对知识点的理解深度。8. 我个人在实际操作中的体会链表这个结构我前前后后教过很多人也帮很多人调试过代码。我发现一个规律凡是能把链表操作写对的人后面学树和图都不会太吃力凡是链表这里卡住的人后面基本一路卡到底。原因很简单链表是第一个要求你精确控制指针的数据结构指针思维是后面所有复杂结构的基础。我自己的经验是链表不要死记代码要理解指针的指向关系。每次写插入或删除之前先在纸上画出操作前后的链表状态标出每个指针的指向然后对照着写代码。写完之后再用边界条件检查清单过一遍。这个方法看起来很慢但比你反复调试改bug要快得多。还有一个建议实验课的时间通常很紧很多人为了赶进度直接抄网上的代码。抄代码不是不可以但你要确保自己能解释每一行的作用能自己写出边界情况的处理。否则实验做完了你还是不会写链表后面学更复杂的数据结构会更痛苦。最后分享一个我常用的调试技巧如果你不确定某个操作是否正确可以在操作前后各打印一次链表对比两次输出。如果链表很长只打印前几个节点和长度也行。这个方法能帮你快速定位问题出在哪个操作上比盲目地读代码效率高很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑