96SEO 2026-02-20 09:55 18
二.单链表的实现1.创建节点的结构2.头文件函数的声明3.函数的实现1打印单链表2创建一个节点3尾插4头插5尾删6头删7查找8在pos位置前插入9在pos位置后插入10删除pos位置11删除pos位置后的节点12清理单链表

三.全部代码1.SList.h2.SList.c3.Test.c
链表有很多种单、双链表循环、非循环链表还有带头、不带头的链表。
本篇的主要内容是单链表无头单向非循环。
1.头部和中间的插入删除效率都较低时间复杂度为O(N)。
需要挪动数据。
2.空间不够用了增容需要申请新空间拷贝数据释放旧空间。
会有不小的消耗。
尤其是异地扩容
3.扩容会有一定的空间浪费。
例如当前容量为100满了以后增容到200我们再继续插入了5个数据后面没有数据插入了那么就浪费了95个数据空间
概念链表是一种物理存储结构上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的
顺序表是数组存储数据的空间是连续的可通过一个指针找到所有的值通过size标记直到没有数据前面的为size的个数即有效数据。
链表的每个节点的大小没有关系也不连续多次malloc开辟出来的空间是随机的。
它是通过一个头指针phead先找到第一个节点然后通过第一个节点的指针找到第二个节点第二个节点的指针找到第三个节点以此类推通过指针链接。
每个位置的节点都有指针指向下一个当下一个为空指针的时候就结束。
看到这有些小伙伴可能有些疑惑链表的每个节点是不连续的为什么上面的两个图中每个节点都有线连接起来变成看似连续的呢其实不是这样的以上的两张图是为了方便理解。
实际在内存中每个节点的地址是随机的只不过用这个节点的指针next找到了下一个节点的地址所以才能实现链接。
链表的功能与顺序表类似无非是增删查改在某位置的插入与删除对数据内容进行管理和操作。
-重定义开始生效的位置因为typedef重定义结构体类型的名称是在上面有箭头的一行开始生效生效了才能使用在前面就提前使用就会出现错误。
创建一个结构体指针变量cur使它指向第一个节点把头指针覆给cur。
利用循环如果cur不是空指针就打印cur所指向的数据然后cur往后走到下一个节点。
直到cur为空跳出最后打印NULL最后一个节点为空指针。
注意与顺序表不同顺序表传过来的指针一定不为空链表传过来的指针可能为空比如链表没有节点头指针指向的就是NULL所以不需要断言头指针。
新节点的类型也是结构体指针用malloc函数开辟一个新节点。
如果新节点为空就报错。
然后给新节点的data赋值next为空返回这个节点方便其他的函数使用
(SLTNode*)malloc(sizeof(SLTNode));if
尾插一个新节点假设有多个节点首先要找到尾定义一个变量tail去遍历链表找到尾
tail刚开始指向第一个节点如果不为空到下一个节点当tail为空时跳出循环把newnode的值新节点的地址赋给tail。
这里有一个问题tail里面存放的是新节点的地址但是原来链表的最后一个节点的next指针并没有存放新节点的地址也就是说最后一个节点没有与新节点连接起来就没有尾插了。
其次还有可能存在内存泄漏新创建的节点丢了。
因为tail是局部变量newnode和phead也是它们出这个函数就销毁了所以给tail这个变量赋新节点的地址没有用。
要成功完成尾插就必须改变结构体的内容让最后一个节点的next指针指向新节点的地址。
这里大家可能有些疑惑既然tail销毁了那么链表的这些节点会不会销毁呢
答案是不会因为这些节点是malloc出来的malloc在堆上开辟的空间只有自己主动free释放掉才能销毁。
首先想到的是要改变结构体节点的内容那么tail这个指针变量就不能到空结束而是到最后一个节点结束tail的next为空就结束tail的位置指向最后一个节点。
此时尾节点的next为空我们要做的是让尾节点的next存放新节点的地址。
让tail的next存放newnode的值新节点的地址就可以改变结构体的内容。
newnode;还有一种情况如果刚开始链表没有节点就不需要找尾了。
直接将新节点的地址给头指针plist就行
一plist没有改变还是指向空指针新节点丢了可能造成内存泄漏。
二newnode和phead是形参形参是实参的拷贝出这个函数就销毁了改变phead并没有改变plist。
注意plist是一级指针改变一级指针需要用到二级指针并且有解引用操作。
所以在函数的参数应该用二级指针来接收传参时plist要有取地址符才能与二级指针类型对应
pphead所指向的就是空指针。
二级指针pphead存放的是一级指针的地址一级指针的地址不可能为空所以二级指针需要断言。
BuySLTNode(x);//原来没有节点改变结构体指针用二级指针if
newnode;}//原来有节点改变结构体用结构体指针else{SLTNode*
头插也需要用到二级指针因为每次头插头指针plist都要连接新的节点。
改变了头指针
注意两者的顺序不能换因为如果先让plist连接newnode那么原来链表plist头指针后面的节点就找不到了。
newnode再连接plist所指向的下一个节点就是自己导致死循环。
前面的尾插、头插都有用到二级指针那么尾删需不需要二级指针呢接下来我们一点一点的分析
这里我们定义一个指针变量tail去找尾把尾节点删掉了那么原来前一个节点变成新的尾节点还需要用另一个变量当作原来尾节点的前一个节点新的尾节点next指针就必须指向NULL只需要改变结构体否则就访问野指针了。
有两种写法这里只展现一种就用tail一个指针变量让它的下一个的下一个指针为空时停下tail-next-nextNULL此时tail-next就是最后一个节点tail是前一个节点修改新的尾节点的next让tail-next为NULL改变结构体就行了。
依图分析只有一个节点时前一个节点就不是节点了是头指针。
要让头指针指向NULL即改变头指针就要用到二级指针了。
{assert(pphead);//空assert(*pphead);//一个节点if
tail-next;}free(tail-next);tail-next
通过前面的分析发现有改变头指针所指向的内容就要用到二级指针头删是把第一个节点除去让头指针指向新的头节点。
只有一个节点和有多个节点不需要分开处理定义一个变量记录原来链表的第二个节点新的头节点free释放掉第一个节点让头指针连接新的头节点
{assert(pphead);//空assert(*pphead);//非空SLTNode*
(*pphead)-next;//注意优先级free(*pphead);//不需要置空因为头指针直接连接新的头*pphead
定义一个变量cur遍历链表先判断cur所指向的数据是否等于x如果相等返回cur否则往后走找不到返回空指针。
100;SLTPrint(plist);}else{printf(找不到\n);}8在pos位置前插入
要在pos位置前插入一个新节点首先pos这个位置的节点必须存在所以要断言pos后面有pos位置插入删除的函数也要用到
pos可能在任意一个位置如果pos在第一个节点就相当于头插了。
头插要改变头指针所指向的内容所以要用二级指针。
直接调用头插的函数即可。
首先要定义一个变量prev遍历链表找到并指向pos的前一个节点因为插入新的节点必须前后连接起来单链表的不足之处后期文章用双向循环带头链表就非常简单。
pos往后走pos时跳出循环让prev-next连接新节点新节点的next连接pos完成插入。
{assert(pphead);assert(pos);//pos在第一个节点就是头插if
因为在pos位置后插入新的节点所以可以不用头指针了找到pos位置的下一个节点即可。
可以定义一个变量posNext为pos位置的下一个节点让新节点的next连接posNextpos-next连接新节点
删除pos位置的节点必须把它的前一个节点与后一个节点连接起来这里就要有头指针去找pos位置的前一个节点。
当pos在第一个节点时就是头删要改变头指针指向的内容所以要用二级指针然后调用头删的函数即可
其实两者的思路是一致的把pos位置的节点删除让前一个节点连接后一个节点就行是尾节点的话让前一个节点连接空指针
*pphead){SLTPopFront(pphead);}else{SLTNode*
要删除pos位置的后一个节点除了pos这个位置要存在之外pos位置的后一个节点也必须存在所以pos-next要断言。
假如pos是在尾节点就没有意义了。
定义一个变量posNext为pos的下一个节点然后使pos-next指向posNext-next即把pos位置的节点与posNext的下一个节点连接起来最后释放掉posNext
{assert(pos);assert(pos-next);//检查是否为尾节点SLTNode*
posNext-next;free(posNext);posNext
清理销毁链表必须要一个一个节点清理因为链表在物理结构上是不连续的。
定义一个变量cur遍历链表每到一个节点把它释放掉。
但是这里又有一个问题当前节点被释放了怎么到下一个节点呢所以我们循环里再定义一个变量next为cur的下一个节点释放完当前的cur然后把next赋给cur这样cur就能到下一个节点了。
最后全部节点释放完头指针要指向空这里又有改变头指针了所以有二级指针。
(SLTNode*)malloc(sizeof(SLTNode));if
BuySLTNode(x);//原来没有节点改变结构体指针用二级指针if
newnode;}//原来有节点改变结构体用结构体指针else{SLTNode*
{assert(pphead);//空assert(*pphead);//一个节点if
tail-next;}free(tail-next);tail-next
{assert(pphead);//空assert(*pphead);//非空SLTNode*
(*pphead)-next;//注意优先级free(*pphead);//不需要置空因为头指针直接连接新的头*pphead
{assert(pphead);assert(pos);//pos在第一个节点就是头插if
*pphead){SLTPopFront(pphead);}else{SLTNode*
{assert(pos);assert(pos-next);//检查是否为尾节点SLTNode*
posNext-next;free(posNext);posNext
5);//测试尾插SLTPrint(plist);SLTPushFront(plist,
40);//测试头插SLTPrint(plist);SLTPopBack(plist);SLTPopBack(plist);SLTPopBack(plist);//测试尾删SLTPrint(plist);SLTPopFront(plist);SLTPopFront(plist);//测试头删SLTPrint(plist);SLTNode*
100;SLTPrint(plist);}else{printf(找不到\n);}SLTNode*
pos4);SLTPrint(plist);}SLTNode*
(pos5){SLTEraseAfter(pos5);SLTPrint(plist);}SLTDestroy(plist);
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback