C——单链表

一.前言

我们在前面已经了解了链表中的双向链表,而我们在介绍链表分类的时候就说过常用的链表只有两种——双向带头循环链表和单向不带头不循环链表。下来我来介绍另一种常用的链表——单向不带头不循环链表也叫做单链表。不清楚链表分类的以及不了解双向链表的可以看我之前的博客C——双向链表-CSDN博客。

二.单链表的结构

我们已经了解了单链表的全称叫做单向不带头不循环链表,我们怎么理解这链表前面的修饰词呢?其实,单链表就像是一节节连接起来的火车车厢一样。但是这节车厢只可以向后走,并且火车的车头和车尾是不能连接在一起的。

我们照着这张图再来分析单链表的结构:单向的意思就是只能沿着D->A->C->E->Z->X->W这个方向走,那不带头的意思就是没有头节点即哨兵位,我们在讲双向链表的时候提到了哨兵位,他只是一个没有有效数据的头节点。不循环的意思我们也可以对照这双向链表来看,双向链表的尾节点可以找到头节点,头节点也可以找到尾节点,这就是循环;而单链表的尾节点指向的不是头节点而是NULL,头节点也不能找到尾节点。

那么单链表的节点到底是什么样的呢?

 我们再来与双向链表的节点进行对比一下:

 单链表节点只有两个成员,分别是数据和指向下一个节点的指针,而双向链表的节点有三个成员,分别是数据,指向前一个节点的指针和指向下一个节点的指针。

综上所述:单链表是一个只能指向下一个节点的链表,而且没有头节点,并且是不循环的。 

三.实现单链表

与双向链表的实现相似,实现单链表也需要很多的函数及头文件,所以我们将所有的函数声明和头文件都放到singlelist.h中,函数的实现都放入singlelist.c中。

3.1单链表节点的结构

//链表节点
struct SingleList
{int val;struct SingleList* next;
};

我们这样写虽然可以完成链表的基本结构,但是难道我们链表只能存储整形嘛?如果我们创建了很多个整型节点,但是有一天我们想利用链表存储字符型或者浮点型数据,那么我们就得一个一个的去修改,费时费力;还有就是这个节点的名称很长,在后面的代码中会重复出现,也很浪费时间。所以我们可以利用typedef关键词,对节点重命名,以及对int重命名。

//节点数据类型
typedef int SingleListdatatype;//链表节点
typedef struct SingleListNode
{SingleListdatatype val;struct SingleListNode* next;
}SLNode;

这样我们在多次使用该类型的时候就不需要再写那么长一串了,以后再修改储存的数据类型的时候也就不用一个一个节点的修改了,只需将定义的节点数据类型中的数据类型修改即可。

3.2单链表节点的创建

 我们在创建节点的时候实际上就是创建一个结构体变量,我们可以利用动态内存管理为我们的每一个节点动态开辟一块内存空间。

//节点的创建
SLNode* BuyNode(SingleListdatatype x)
{SLNode* node = (SLNode*)malloc(sizeof(SLNode));if (node == NULL){perror("malloc");exit(-1);}node->val = x;node->next = NULL;return node;
}

 我们来测试一下节点创建函数是否正确:

我们利用此测试代码来调试发现,我们创建了4个节点,通过调试也确实观察到了这四个节点,并且它们储蓄的数据就是我们传的数据,指向下一个节点的指针也是NULL。与我们想要实现的结果相同。 

那我们现在将这四个节点连接起来:

void test1()
{SLNode* plist = NULL;SLNode* plist1 = BuyNode(1);SLNode* plist2 = BuyNode(2);SLNode* plist3 = BuyNode(3);SLNode* plist4 = BuyNode(4);plist1->next = plist2;plist2->next = plist3;plist3->next = plist4;
}int main()
{test1();return 0;
}

我们再来调试一下: 

我们看到将所有节点连接起来之后,我们就可以利用第一个节点找到后面的节点。 

3.3链表的打印

//链表的打印
void SLNodePrint(SLNode* plist)
{while (plist)//plist != NULL{printf("%d->", plist->val);plist = plist->next;}printf("NULL\n");
}

 我们再来测试一下该函数:

也是没有出现问题,说明我们链表的打印已经完成了。 

3.4尾插

我们现在创建的节点需要我们手动将其连接起来,我们可以利用尾插方法,将新创建的节点直接连接到前一个节点上。 

//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x)
{assert(plist);SLNode* newnode = BuyNode(x);//新节点if (*plist == NULL){//空链表*plist = newnode;}else{//非空链表//遍历原链表,找到尾节点SLNode* pcur = *plist;while (pcur->next)//(*plist)->next != NULL{pcur = pcur->next;}pcur->next = newnode;}
}

我们测试下尾插方法:

我们尾插了四次,通过打印方法,将链表打印出来,说明我们的尾插方法也没有问题。但是有些人可能会有疑问:为什么这里传的是二级指针 而不传一级指针?

因为我们是通过一个结构体类型的指针来维护我们的链表,而我们传一级指针的话相当于值传递,而对于值传递来说,形参改变是不影响实参的。简单来说,就说你在函数内部将形参节点的next指针改变了,但是却不影响实参的值。达不到我们的目的。所以要想形参的改变影响实参的话,我们就得地址传递,就得传一级指针的地址,也就是二级指针

3.5头插 

头插从名字就可以听出来是把一个新节点插到链表的头部,我们利用图来分析如何进行头插:

//头插
void SLpushfront(SLNode** plist, SingleListdatatype x)
{assert(plist);SLNode* newnode = BuyNode(x);newnode->next = *plist;*plist = newnode;
}

我们测试头插方法:

根据打印结果来看,头插的方法也没有任何问题。 

3.6尾删

尾删就是删除链表中的尾节点,那么前提就是该链表不能为空,如果都是空链表了,怎么执行删除操作呢?我们画图来分析一下:

//尾删
void SLpopback(SLNode** plist)
{assert(plist && *plist);//*plist != NULL防止链表为空SLNode* pcur = *plist;//记录倒数第二个节点SLNode* del = *plist;//记录尾节点while (del->next){pcur = del;del = del->next;}pcur->next = NULL;free(del);del = NULL;
}

我们测试之后发现出了错误,这是为什么呢?

我们进行四次尾删,最后一次删除会将1删除掉,此时应该只打印NULL,这里打印了随机值。这是为什么呢? 

//尾删
void SLpopback(SLNode** plist)
{assert(plist && *plist);//*plist != NULL防止链表为空if (!((*plist)->next))//(*plist)->next == NULL{//链表只有一个节点free(*plist);*plist = NULL;}else{//链表有多个节点SLNode* pcur = *plist;//记录倒数第二个节点SLNode* del = *plist;//记录尾节点while (del->next){pcur = del;del = del->next;}pcur->next = NULL;free(del);del = NULL;}
}

我们现在再来测试一下修改后的尾删代码: 

我们看到,现在就没有问题了。

3.7头删

我们实现了尾删,现在来实现头删。头删的前提与尾删的前提相同:链表不能为空。头删实际上就是删除链表中的第一个节点,我们画图分析如何实现头删:

//头删
void SLpopfront(SLNode** plist)
{assert(plist && *plist);//链表不能为空SLNode* next = (*plist)->next;free(*plist);*plist = next;
}

我们来测试头删方法:

没有问题~ 

那如果我们多删一次呢?

我们看到,第五次删除的时候这时候已经是空链表了,我们再看提示的错误信息,说是断言出错了,这就说明了链表为空了。 

3.8查找数据

查找数据非常简单,我们只需要遍历链表,将要查找的数据与链表中的数据进行对比,如果找到了则返回该数据对应节点的地址;如果没找到则返回NULL。

//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x)
{assert(plist);SLNode* pcur = plist;while (pcur){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}

测试查找:

结果没毛病,老铁OK了! 

3.9在指定位置之前插入数据

我们要在指定位置的前面插入数据,所以我们必须得找到该指定位置,这就需要调用我们的查找方法了。我们来画图分析一下:

//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos, SingleListdatatype x)
{assert(plist && *plist);//链表不能为空,否则找不到指定位置if (pos == *plist){//pos节点就是第一个节点//调用头插方法SLpushfront(plist,x);}else{//pos节点不是第一个节点SLNode* newnode = BuyNode(x);SLNode* pcur = *plist;SLNode* prev = *plist;while (pcur){if (pcur->val == pos->val){break;}prev = pcur;pcur = pcur->next;}// prev newnode pcur/posprev->next = newnode;newnode->next = pcur;}
}

 我们测试该代码:

我们测试了三种位置的插入,无论是第一个节点之前还是中间节点之前或者是尾节点之前都没有问题。

3.10在指定位置之后插入数据

在指定位置之后插入数据与在指定位置之前插入数据有相同点但也有不同点,我们继续来画图分析:

//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x)
{assert(pos);SLNode* newnode = BuyNode(x);//pos newnode pos->nextnewnode->next = pos->next;pos->next = newnode;
}

我们对其进行测试来看: 

3.11删除pos节点

我们现在来删除指定节点,前提肯定是链表不能为空。我们接着来画图分析:

//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos)
{assert(plist && *plist);assert(pos);if (*plist == pos){//pos是头节点,调用头删方法SLpopfront(plist);}else{SLNode* prev = *plist;while (prev->next != pos){//找到pos的前一个节点prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}

测试该代码: 老铁没毛病!

3.12删除pos节点之后的节点

删除pos节点之后的节点我们要改变指向的指针就只有一个了,那就是pos->next指针。要让它指向pos->next->next。我们画图分析:

//删除pos节点后一个节点
void slpopposback(SLNode* pos)
{assert(pos && pos->next);//pos->next != NULL避免pos是尾节点SLNode* Next = pos->next;pos->next = pos->next->next;free(Next);Next = NULL;
}

 测试代码:

那如果我们的pos节点就是尾节点程序会发生什么呢? 

我们看到,上面提醒我们断言出错,说明此时pos节点为尾节点。 

3.13销毁链表

//销毁链表
void DestorySL(SLNode** plist)
{assert(plist && *plist);SLNode* Next = (*plist)->next;while (Next){free(*plist);*plist = Next;Next = Next->next;}free(*plist);*plist = NULL;
}

 我们通过调试来判断我们的销毁方法:没有问题!

到这里,我们单链表的所有方法都已经完成了。下面附上完整代码:

四.完整代码

1.singlelist.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//节点数据类型
typedef int SingleListdatatype;//链表节点
typedef struct SingleListNode
{SingleListdatatype val;struct SingleListNode* next;
}SLNode;//节点的创建
SLNode* BuyNode(SingleListdatatype x);//链表的打印
void SLNodePrint(SLNode* plist);//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x);//头插
void SLpushfront(SLNode** plist, SingleListdatatype x);//尾删
void SLpopback(SLNode** plist);//头删
void SLpopfront(SLNode** plist);//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x);//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos,SingleListdatatype x);//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x);//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos);//删除pos节点后一个节点
void slpopposback(SLNode* pos);//销毁链表
void DestorySL(SLNode** plist);

2.singlelist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"//节点的创建
SLNode* BuyNode(SingleListdatatype x)
{SLNode* node = (SLNode*)malloc(sizeof(SLNode));if (node == NULL){perror("malloc");exit(-1);}node->val = x;node->next = NULL;return node;
}//链表的打印
void SLNodePrint(SLNode* plist)
{while (plist)//plist != NULL{printf("%d->", plist->val);plist = plist->next;}printf("NULL\n");
}//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x)
{assert(plist);SLNode* newnode = BuyNode(x);//新节点if (*plist == NULL){//空链表*plist = newnode;}else{//非空链表//遍历原链表,找到尾节点SLNode* pcur = *plist;while (pcur->next)//(*plist)->next != NULL{pcur = pcur->next;}pcur->next = newnode;}
}//头插
void SLpushfront(SLNode** plist, SingleListdatatype x)
{assert(plist);SLNode* newnode = BuyNode(x);newnode->next = *plist;*plist = newnode;
}//尾删
void SLpopback(SLNode** plist)
{assert(plist && *plist);//*plist != NULL防止链表为空if (!((*plist)->next))//(*plist)->next == NULL{//链表只有一个节点free(*plist);*plist = NULL;}else{//链表有多个节点SLNode* pcur = *plist;//记录倒数第二个节点SLNode* del = *plist;//记录尾节点while (del->next){pcur = del;del = del->next;}pcur->next = NULL;free(del);del = NULL;}
}//头删
void SLpopfront(SLNode** plist)
{assert(plist && *plist);//链表不能为空SLNode* next = (*plist)->next;free(*plist);*plist = next;
}//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x)
{assert(plist);SLNode* pcur = plist;while (pcur){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos, SingleListdatatype x)
{assert(plist && *plist);//链表不能为空,否则找不到指定位置assert(pos);if (pos == *plist){//pos节点就是第一个节点//调用头插方法SLpushfront(plist,x);}else{//pos节点不是第一个节点SLNode* newnode = BuyNode(x);SLNode* pcur = *plist;SLNode* prev = *plist;while (pcur){if (pcur->val == pos->val){break;}prev = pcur;pcur = pcur->next;}// prev newnode pcur/posprev->next = newnode;newnode->next = pcur;}
}//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x)
{assert(pos);SLNode* newnode = BuyNode(x);//pos newnode pos->nextnewnode->next = pos->next;pos->next = newnode;
}//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos)
{assert(plist && *plist);assert(pos);if (*plist == pos){//pos是头节点,调用头删方法SLpopfront(plist);}else{SLNode* prev = *plist;while (prev->next != pos){//找到pos的前一个节点prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}//删除pos节点后一个节点
void slpopposback(SLNode* pos)
{assert(pos && pos->next);//pos->next != NULL避免pos是尾节点SLNode* Next = pos->next;pos->next = pos->next->next;free(Next);Next = NULL;
}//销毁链表
void DestorySL(SLNode** plist)
{assert(plist && *plist);SLNode* Next = (*plist)->next;while (Next){free(*plist);*plist = Next;Next = Next->next;}free(*plist);*plist = NULL;
}

3.test.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"//void test1()
//{
//	SLNode* plist1 = BuyNode(1);
//	SLNode* plist2 = BuyNode(2);
//	SLNode* plist3 = BuyNode(3);
//	SLNode* plist4 = BuyNode(4);
//
//	plist1->next = plist2;
//	plist2->next = plist3;
//	plist3->next = plist4;
//
//	SLNodePrint(plist1);
//}void test2()
{SLNode* plist = NULL;//测试尾插SLNodepushback(&plist, 1);SLNodepushback(&plist, 2);SLNodepushback(&plist, 3);SLNodepushback(&plist, 4);SLNodePrint(plist);//测试销毁链表DestorySL(&plist);//测试删除pos节点的下一个节点//SLNode* find = SLFind(plist, 4);//slpopposback(find);//SLNodePrint(plist);//测试删除pos节点//SLNode* find = SLFind(plist, 4);//slpoppos(&plist,find);//SLNodePrint(plist);//测试在指定位置之后插入数据//SLNode* find = SLFind(plist, 1);//SLposback(find,66);//SLNodePrint(plist);//测试在指定位置之前插入数据//SLNode* find = SLFind(plist, 4);//SLposfront(&plist,find,100);//SLNodePrint(plist);//测试查找//SLNode* find = SLFind(plist,99);//if (find == NULL)//{//	printf("找不到!");//}//else//{//	printf("找到了!");//}//测试头删//SLpopfront(&plist);//SLNodePrint(plist);//2 3 4//SLpopfront(&plist);//SLNodePrint(plist);//3 4//SLpopfront(&plist);//SLNodePrint(plist);//4//SLpopfront(&plist);//SLNodePrint(plist);//NULL//SLpopfront(&plist);//SLNodePrint(plist);//测试尾删//SLpopback(&plist);//SLNodePrint(plist);//1 2 3//SLpopback(&plist);//SLNodePrint(plist);//1 2//SLpopback(&plist);//SLNodePrint(plist);//1//SLpopback(&plist);//SLNodePrint(plist);//NULL//测试头插//SLpushfront(&plist,100);//SLNodePrint(plist);//SLpushfront(&plist,99);//SLNodePrint(plist);
}int main()
{//test1();test2();return 0;
}

完!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/687861.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【bug记录】Vue3 Vant UI 中 van-popup 不弹出

原因&#xff1a;语法使用错误&#xff0c;使用了 Vue 2 的语法 Vue3语法&#xff1a; Vue2语法&#xff1a;

前端Vue uView 组件<u-search> 自定义右侧搜索按钮样式

前言 uView 文档的效果不是ui设计的样式 需要重新编辑 原效果 ui设计效果 解决方案 设置里说明的需要传一个样式对象 这个对象 需要写在 script 标签里面 这里需要遵循驼峰命名 比如font-size 改为 fontSize lineHeight和textAlign为水平锤子居中效果 searchStyle: {ba…

Linux虚拟机运行“yum install gcc-c++”报错“Loading mirror speeds from cached hostfile”

目录 一、在Linux上安装Redis时&#xff0c;在终端执行命令“yum install gcc-c”时&#xff0c;报错&#xff1a; 二、然后发现linux的网络不通&#xff0c;什么网站都访问不了 三、连上网后&#xff0c;再变换yum源 四、重新运行yum install gcc 一、在Linux上安装Redis时…

WPF之工具栏菜单栏功能区。

1&#xff0c;菜单栏&#xff0c;工具栏&#xff0c;状态栏。 1.1&#xff0c;Menu中可添加菜单分隔条<Separator></Separator>作为分割线&#xff0c;使用Separator可以通过改变其template来自定义&#xff0c;Separator是无焦点的&#xff0c;如果简单的在MenuIt…

IIS配置SSL,根据pem和key生成pfx,openssl的版本不能太高

1、生成pfx文件 供应商给的文件是pef和key后缀的两个文件&#xff0c;在IIS里不好导入(如果有知道好导入的可以给我留言&#xff0c;谢谢。)。 1.1 下载OpenSSL工具&#xff0c;并安装。 主要用于将.pem文件转成.pfx文件。 下载OpenSSL的链接&#xff1a;http://slproweb.com/…

Oracle 多表查询

关联查询 一、sql:1992语法的连接笛卡尔积等值连接非等值连接自连接外连接 二、sql:1999语法的连接交叉连接自然连接USING创建连接ON创建连接左外连接右外连接FULL OUTER JOININNER JOIN 三、子查询子查询的种类单行子查询多行子查询 在From字句中使用子查询练习 四、行转列 一…

视频拼接融合产品的产品与架构设计(二)

视频拼接融合产品的产品与架构设计一 以上是第一期&#xff0c;以前思考的时候还是比较着急&#xff0c;现在思考的更多了&#xff0c;现实世界的拼接更加需要我们沉下心来做&#xff0c;尤其是对于更多画面&#xff0c;画面更加清晰怎么做 本篇章不在于其他功能&#xff0c;在…

TL(TypeLetters)的另类用法:自动去空行

你可能还不知道吧&#xff1a;可以使用TL&#xff08;TypeLetters&#xff09;自动去掉文件的空行。 先看文件&#xff1a;文件中有很多空白行&#xff0c;我们可以手动去把它们一行一行的删除掉。 有了TL&#xff08;TypeLetters&#xff09;&#xff0c;一切都简单了&#x…

mongoDB分组查询

完整代码 //根据医院编号 和 科室编号 &#xff0c;查询排班规则数据Overridepublic Map<String, Object> getRuleSchedule(long page, long limit, String hoscode, String depcode) {//1 根据医院编号 和 科室编号 查询Criteria criteria Criteria.where("hosco…

SQL的命令

目录 创建用户 ​编辑 DDL数据库操作 查询 创建 使用 删除 创建数据库表 在表中修改字段 查询表 DML 添加数据 修改 删除 DQL 查询 创建用户 DDL数据库操作 查询 show databases; 创建 权限问题导致无法创建&#xff0c;连接root修改用户权限 CREATE DATABAS…

后端的一些科普文章

后端开发一般有4个方面 后端开发流程 1阶段 域名认证 是每一个计算机在网络上有一个ip地址&#xff0c;可以通过这个地址来访问102.305.122.5&#xff08;举例&#xff09;&#xff0c; 但是这个公网ip地址&#xff0c;比较难记忆&#xff0c;所以大家使用域名来更好的记忆…

你可能喜欢但也许还不知道的好用网站-搜嗖工具箱

在线工具 https://www.zxgj.cn/ 作为一个工作生活好帮手&#xff0c;在线咨询网站提供了丰富的实用功能&#xff0c;从工作中的图表制作、图片修改到生活中的各种测试、健康、娱乐、学习、理财等等涵盖面很广。 在线工具网站从界面和操作上来看对用户也很友好&#xff0c;页面…