“车裂”链表---单链表

目录

前言:

这一篇将会总结初阶的数据结构---链表,保括单链表和双向循环带头链表,由于数据结构这部分依靠画图,一般都是先有图,再有代码,所以数据结构这一章将会采用图形结合的方法,然后并分点解释代码中要注意的细节的部分。

注:本篇命名形式采用c++stl库中官方的命名方式,方便以后的学习,且前期初阶数据结构都采用c的方式编写,后期进阶一点的数据结构将采用c++编写

单链表:

1.了解单链表的存储结构:

2.遍历这个单链表

3.实现链表的基本功能——增删查改等(实现创建节点的函数

4.头插

5.尾插

6.尾删

这里再提供一个思路:

7.头删

8.查找

9.修改

10.在pos位置之前插入数据

11.在pos之后插入数据

12.删除pos

13.尾删---删pos后的位置

14.销毁链表

总结:

单链表一章到此结束,有很多细节像pos之前插入数据需要注意,一定先有图再有代码,期待下一章双向循环链表吧!


前言:

这一篇将会总结初阶的数据结构---链表,保括单链表和双向循环带头链表,由于数据结构这部分依靠画图,一般都是先有图,再有代码,所以数据结构这一章将会采用图形结合的方法,然后并分点解释代码中要注意的细节的部分。

注:本篇命名形式采用c++stl库中官方的命名方式,方便以后的学习,且前期初阶数据结构都采用c的方式编写,后期进阶一点的数据结构将采用c++编写

单链表:

1.了解单链表的存储结构:

在一块空间里有两部分:存放数据和指向下一块空间的指针,且这一块空间和下一块空间的类型是完全一样的,怎么样,是不是很简单,这样我们就联想到要用什么语法来完成这样的存储呢?没错,是结构体,并且为了保持空间的一致性,所以结构体里存放下一块空间的指针类型还要是这个结构体,字面说起来很绕(其实后面还有更绕的:树),看图:

这里需要解释的只有,typedef的int,因为存储的数据不一定是整型,所以为了以后的修改不比要每个地方都改,所以用typedef。

2.遍历这个单链表

如何遍历?直接从图上理解(图画的随意了一些,见谅):

第二个图标明了地址,所以就能更好的理解了一块空间里存放的指向下一块空间的指针,指向的地址,就是下一块空间的地址,那问题又来了?如何让这个链表顺理成章的串联起来呢?

1.定义cur是为了不改变原指针的前提下,让cur往下走。

2.cur=cur->next是链表里面的关键代码,它起到的作用就是让链表串联起来,phead就指向链表的指针,用cur代替phead往下走,cur->next这句代码的意思就是一块空间的后一个部分存放的就是指向下一块空间的指针,再给给cur就是说明这个指针就是指向下一块空间的。

3.由于这里并没有改变一级指针plist的值,所以就用一级指针接收,后面要改变一级指针的值(怎么还能改变指针的值?其实是改变的就是指针指向的空间也就是结构体的内容),就要传一级指针的地址,用二级指针接收。

3.实现链表的基本功能——增删查改等(实现创建节点的函数)

完成了遍历,但现在是一个空链表啊,我们就要往里面插入数据,有了插入,就要有删除,那就要涉及是在头还是尾还是中间插入呢?然后我们要实现一下释放空间的函数。那我们实现是不是要插入某个节点,为了方便,我们让它封装到一个函数里面:

1.由于我们要返回这个节点,所以就不能用临时的变量,所以就用到了malloc开辟的在堆上的节点,注意要判空。

2.然后就是放入数据,新开的节点的next位置暂且为NULL,因为还不知道指向谁嘛。

4.头插

1.由于要改变一级指针的指向了,所以传一级指针的地址,并用二级指针接收

2.断言pphead是因为pphead是指向链表空间的指针,是地址,就算头指针为空,NULL指针的地址也不能为空,不断言*pphead是因为空链表也能插入,也就是让新节点存放的指针指向NULL,可以有*pphead==NULL

3.*pphead就是找到plsit的指向,可以理解就是plist

4.让要头插的节点即新节点的next指向头节点后,再让头插的节点成为新的头节点,起初NULL指针就是头节点

结合遍历单链表的打印函数,可以看到单链表的雏形了:

5.尾插

1.空链表也可尾插,也就是让尾插的节点成为第一个节点也是尾节点,所以不能断言*pphead,可以有*pphead==NULL。

2.既然*pphead可以为NULL,那就要分情况讨论(为什么要分情况?看下面),当为空链表,直接让尾插的节点成为新的头节点,同时也是尾节点,由于newnode在创建的时候它的next就已经指向NULL了,不用再写newnode->next=NULL了

3.当不为空链表的时候,就要找尾指针了,找尾指针,跟上面一样,先用一个tail指针往后走,当tail指针的next指向空的时候停下来,所以不等于NULL为循环条件,找到尾指针后,让尾指针的next指向newnode即可,同样的,由于newnode在创建的时候它的next就已经指向NULL了,不用再写newnode->next=NULL了。

4.为什么要分情况也就明白了,因为循环的条件是tail->next!=NULL,如果不分情况,NULL->next就对NULL解引用了,肯定不对

6.尾删

1.这里*pphead就不能为空了,因为空链表不能删,其次后面还要对*pphead解引用,不能为空,

要对其断言,当然也可以采用温柔的检查,判断*pphead如果为空,直接返回或者手动报错即可。

2.当有一个节点时(为什么要分一个节点和多个节点?看下面),也就是*pphead的next为空时,直接释放掉*pphead,然后还要置空,因为空链表还要保留一个NULL

3.当有多个节点时,就要找到尾节点的前一个节点,释放掉尾节点,再让尾节点的next指向NULL,注意这里要先释放尾节点(先释放尾节点不会导致链表断裂),因为如果先让尾节点的前一个节点指向NULL,再释放就找不到尾节点了,并且后释放的就不是尾节点了,释放的就是NULL了。

4.因为多个节点中找尾节点的前一个节点的循环条件是tail->next->next!=NULL,如果不分出一个节点的情况,就又对NULL指针解引用了。

这里再提供一个思路:

这里其实就是多定义了一个prev指针找tail的前一个节点,free掉tail后再将prev的next指向NULL即可,这里就运用了温柔的检查空链表的写法。

7.头删

1.空链表和一个节点的情况和尾删一样,不再赘述。

2.这里要头删就要先找到头节点的下一个节点,由于要释放头节点,所以要先让头节点保留下来,以防找不到头节点的下一个节点;同时也可保留头节点的下一个节点,释放掉头节点后,再让头节点的下一个节点成为新的头节点。

3.这里也提供了一个节点和多个节点的一起写法,其实就是让空指针成为新的头,再释放掉头节点。

8.查找

1.找到就返回这个节点,找不到就返回NULL,这个比较简单。

2.没有改变一级指针的指向,不需要用二级指针

9.修改

1.返回的是一个结构体的指针,可以通过这个指针修改数据

2.传值返回,将cur拷贝给临时变量,临时变量再给给pos

10.在pos位置之前插入数据

void SLInsertBeforePos(SLNode*& phead, SLNode* pos, SLDataType x)
{
	assert(phead);
	assert(pos);
	SLNode* newnode = BuySLTNode(x);

	if (phead == pos)
	{
		//复用
		//SLPushFront(pos, x);//为什么传pos不行?为什么传pos不行?因为传进来的pos是个节点,调用SLPushFront需要传的是指向链表的头指针,pos虽然也是指向链表的,但是最后打印函数中传的是plist

		//newnode->next = pos;
		//phead = newnode;
	}
	else
	{
		SLNode* cur = phead;
		while (cur->next != pos)
			cur = cur->next;
		newnode->next = cur;
		cur->next = pos;
	}
}

1.phead一样要断言,他是指向链表的指针,存放的是地址,就算是空指针地址也不是空;pos可以断言,因为要在pos的前面插入数据,pos又给成空,你什么意思?要是理解成空链表的头插尾插也可以,反正最好断一下,数据结构还是很灵活的;而*pphead如果不放心传过来的是空链表,后面又要对它解引用,也可以断一下。

2.要分情况讨论pos的位置,如果pos就是头节点,那prev就是pos,那else里面的循环条件就会一直成立,所以要分情况

3.如果是pos是头节点即*pphead==pos,那就让新节点的next指向pos即可,也可以复用头插的逻辑,传pphead,pphead现在是二级指针,所以是plist的地址,相当于&plist

4.如果pos位于中间,就找到pos的前一个节点(还要用循环,后面双向链表会更方便),然后再让新节点的next指向pos,再让pos前一个节点的next指向新节点(这两个逻辑可以反,链表不会断,因为就算让pos的前一个节点的next指向新节点,pos已经确定了,不会找到,而链表断是找不到前一个节点或者后一个节点了

关于为什么传pos打印出来没有效果,因为打印的是原链表;下面删除pos位置也是这个道理,pos被delete了,指向的空间就没了,所以下面传pos会直接报错了:

11.在pos之后插入数据

12.删除pos

1.首先需要考虑pos位于开头,在pos在开头的情况下还要考虑一个节点和多个节点,而头删已经实现好这几个逻辑了,直接复用

2.当pos位于中间的时候,找pos的前一个节点,将pos的前一个节点的next指向pos的next,释放掉pos即可

3.断言*pphead也可以,如果不放心空链表不能删的情况,但其实都找到pos了,也不会是空,可以不断

13.尾删---删pos后的位置

1.要找到pos后一个的后一个,就要声明pos的后一个不能为空(因为解引用),所以就不用考虑一个节点的情况了,同时也不能删NULL指针。

2.还要记录下来pos->next的位置,因为如果直接pos->next=pos->next->next的话,再释放pos->next就不对了。

14.销毁链表

要用一级指针,就要在外面置空:

用二级:

总结:

单链表一章到此结束,有很多细节像pos之前插入数据需要注意,一定先有图再有代码,期待下一章双向循环链表吧!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值