线性表——单链表的增删查改操作

一.认识单链表

目录

一.认识单链表

1.什么是单链表呢?

2.结点的初始化

二.单链表的增删查改操作

1.单链表的头插操作

2.单链表的尾插操作

3.指定位置的前方和后方进行插入

1.在p1的前面插入ps

4.单链表的删除操作

1.中间位置删除

2.头删

3.尾删


1.什么是单链表呢?

单链表也属于线性表中的一种,它是物理上不连续存储,逻辑上用指针按次单向序链接的线性表。

如:

各个结点之间并无物理上的链接。各个结点由两部分组成------数据域和指针域,各个结点的指针指向它的下一个结点,若没有下一个节点,则使该结点的指针赋为空。

如此,就是一个单向、不连续存储、不循环的线性表------单链表。

2.结点的初始化

单链表虽然物理上不连续,但是并代表它的各个结点的数据类型可以各不相同,和顺序表一样,各个储存的是相同的数据类型。

复制代码
1 struct SListNode
2 {
3 int data; //节点数据
4 struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
5 };

每当我们插入一个结点时,都需要开辟一块空间,用来存储结点,但是插入结点后,我们基本上只知道第一个结点的指针,后面的结点都时匿名的 。

**头结点:**也叫哨兵位。只是一个放哨的,不存储任何数据,指针域的指针指向第一个有效结点的地址。当然头结点可用可不用。

我们先在头文件对结点的结构体进行声明

同时我们还定义了结构体的指针,*PNode相当于

复制代码
typedef struct SLNode* PNode;

此时PNode就是一个指针类型,注意是类型而不是变量,当我们再声明一个指针变量时就可以这样声明了

复制代码
PNode L;
//相当于struct SLNode* L;

现在我们来完成各个结点的初始化函数,当然在那之前我们先完成头节点的初始化

思考一下,我们初始化一个结点,应该返回是什么或者应该什么都不返回吗?

我们的选择是要,如果不反回结点的地址,就需要二级指针进行操作,这里我们希望使用一级指针,我们选择返回初始化的结点的地址

我们知道头结点的数据是不用初始化的,我们只管初始化后把地址返回去即可。

如果初始化成功就返回开辟好的地址,如果初始化失败就返回空。(同时在头文件上声明)

在test.c文件中进行调用

注意我们创建的头结点是不存放数据的,当然刚初始化头节点,它后面还没有有效的结点,所以node的next要置为空

二.单链表的增删查改操作

1.单链表的头插操作

前面我们已经创建了一个头节点,但是头插法指的是在第一个有效数据前面插入,而不是头节点的前面插入。理解这个后我们开始实现头插函数

我们来分析应该怎么插入

假设ps指针指向的就是我们要插入的结点,我们最直接的想法就是让phead指向ps,然后ps指向p1,当然,这没错,只不过需要注意顺序问题。如果先让phead的next先指向ps,那么我们就失去了p1的地址,也就是这种写法

复制代码
phead->next = ps;
ps->next = phead->next;

这样就容易造成了死循环,也就变成了这样

虽然看似简单,稍不注意就会犯这样的错误

那么正确的操作应该是这样

复制代码
//先让ps找到下个结点的地址
ps->next = phead->next;
//再让头节点的next指向ps
phead->next = ps;

这里也能体现出带头节点的好处,假如头节点指向的下一个位置为空,我们也不用进行额外的操作,依然和正常头插一样即可。如果是不带头节点的头插,你还得判断头指针是否为空,操作不慎还会使得头指针变成野指针,导致越权访问。

现在头插的基本逻辑已经搞定,头插函数就可以这么写了

我们需要头指针,你既然要插入,肯定要有值吧,所以还得有value,下面就是用上面的逻辑来实现

这样我们就完成了头插,让我们来测试一下,当然为了只管观察过程,我么设计一个打印函数(这里不细讲)

来我们看测试结果

没有问题,我们继续看尾插

2.单链表的尾插操作

我们来看图进行分析

为了方便解说我们给最后一个结点取名为p1,在实际操作过程中它是匿名的,我们只能通过头节点开始从头遍历找到它。

现在我们想把ps插入到p1的后面,和头插法的逻辑很像,先让ps指向p1的next,再让p1指向ps即可,那就是这样

由于逻辑和头插几乎一直,故不再过多解释,我们直接测试结果

很简单,现在我们来看指定位置的前后面插入

3.指定位置的前方和后方进行插入

这里我们先来解决传入头节点传入的情况

假设我要在p1这个位置的前面进行插入操作,现在我们有两种方法进行寻值操作,第一种是按需要进行寻值插入,第二种是先查询值的位置再进行操作。这两种既然都涉及查询,我就先完成查询函数吧,然后再进行插入操作,查询函数比较简单,这里直接给出

这样得到要查询的值的地址后就方便进行前后插入操作了。

1.在p1的前面插入ps

假设查找到了p1的地址,现在要在p1的前面插入ps,但是现在不传入头指针了,这就意味着我没有办法从头开始遍历找到p1的前一个结点,就没有办法让p1的前一个结点的next指针直接指向ps

能想到的一个办法是先把ps插入到p1的后面做它的直接后继结点,然后把p1的data与ps的data交换,以实现在值value前插入结点ps的操作

用代码实现就是

同时在头文件声明后测试

出现了随机值,说明插入函数又未定义值就进行运算的情况,检查函数

复制代码
bool InsertFront(PNode p1, ElemType value)
{
	PNode temp = (PNode)malloc(sizeof(Node));
	if (temp)
	{
		temp->next = p1->next;
		p1->next = temp;
		p1->data = temp->data;
		p1->data = value;
		return true;
	}
	return false;
}

我发现,在两个结点完成指针指向链接后,temp的data并没有进行初始化就与p1的data进行了交换值操作,因此交换后temp的值为确实值,而p1的data为随机值,只需要开在交换值前给temp的成员data赋值即可

修改完成,测试运行结果

在4的前面插入11,再于2的前面插入12均没有问题。

在p1的后方插入值value和前插中的操作如出一辙,直接插入值,不需要进行交换操作

在头文件声明后测试

分别于4的后面插入11,再于2的后面插入12 。代码正常进行

插入操作基本完成,既然后插入肯定也要又删除操作。

4.单链表的删除操作

1.中间位置删除

删除操作和插入操作很像,都是注意结点的指针域的指向,释放空间后不要让表断链

假设传入参数是只传了p1指向的地址,就无法从头开始找到p1的前驱结点,不能简单地去修改p1前驱结点的next指向,这个时候只能接着运用偷梁换柱的方法,把删掉p1修改为删掉p1后继结点,因为这里讨论的是p1不为最后一个结点的情况,所以不用担心p1后面没有值和p1前驱结点的next会成为野指针的情况。

把p1后继结点的data赋值给p1的data,在让p1的next等于p1后继结点的next,这样就完成了偷梁换柱

就变成了这样

测试一下看效果

出问题了,目前不知道哪里有bug,通过调试检查试试

发现我们要删除值为4的结点进行删除操作后访问错误

检查后发现是删除时空间释放的逻辑有问题,当完成交换后,node指向的已经时原来直接后继的next,再进行释放操作就会释放到原直接后继结点的next,现在只需要另起一个变量存储原直接后继结点的地址,然后释放就不会释放错空间了

在来测试看结果

结点4和结点2都被正常删除,没有造成内存泄漏的情况。

2.头删

因为讨论的时有带头节点的单链表,所以头山的时候完全不用考虑头节点是否为空的情况,大大方便了删除操作的执行。

结合前面的经验,每把phead的next修改前,先把要释放的空间临时存起来,然后free,但是头删要注意phead的next为空的情况,所以进行头删操作前要先判断phead的next是否为空,为空则返回。

同时在头文件进行声明,测试看结果如何

结果正常,接着继续处理尾删

3.尾删

尾删就是从最后一个结点挨个往前删除结点,但是单向链表是不可以逆向访问的,每一次尾删只能从头循环遍历到最后一个结点,再进行删除操作。同样,尾删时,如果只有一个元素时或者链表为空,头指针的next就为空,所以尾删也要判断phead的next是否为空。

如果我的pos时最后一个结点,我释放掉pos后,pos的直接前驱结点的next就变成了也指针,如果我把循环条件改为

复制代码
while (pos->next && pos->next->next)

确实可以解决这个问题,但是有效元素为1的时候,如何判断我的pos是倒数第一个元素还是倒数第二个元素?

只需要让pos是从phead的位置出发即可,这里再一次体现出带头节点的单向链表的优势

循环结束后,pos的next才是最后一个结点,所以要释放的是pos->next,然后把pos的next置为空

这样我们就完成了尾插操作。

至此我们就完成了单链表的基本增删查改操作

相关推荐
SunnyByte6 小时前
线性表——双向链表
c语言·链表
jimy16 小时前
C 语言的 static 关键字作用
c语言·开发语言·算法
handler017 小时前
算法:图的基本概念
c语言·开发语言·c++·笔记·算法·图论
木木_王7 小时前
嵌入式Linux学习 | 数据结构 (Day03)顺序表与单链表 超详细解析(含 C 语言实现 + 作业 + 避坑指南)
linux·c语言·数据结构·学习
wefg18 小时前
【C语言】用 C 语言实现多态
c语言·开发语言
我不是懒洋洋8 小时前
手写一个B+树:从原理到数据库索引实战
c语言·c++·经验分享
leo__52019 小时前
IEC 104 协议 C 语言实现
c语言·数据库
啧不应该啊20 小时前
Day1 Python 与 C 的类型区别
c语言·开发语言
cen__y21 小时前
Linux07(信号01)
linux·运维·服务器·c语言·开发语言