C语言数据结构与算法(链表)

1.链表概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。

现实中:
         
数据结构中:
物理结构(实实在在数据在内存中的变化)
逻辑结构(为了方便理解,形象的画出来的)

2.链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构

1、单向或者双向

2、带头或者不带头

3、循坏或者非循环

        

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都

是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带

来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3.无头单向非循环链表的实现(增删查改)

3.1结构体的设定

                                        

这里利用了typedef重命名,便于识别及代码实现。

3.2尾插

注意:

①空链表尾插(需改变头指针指向的地址,切记要传头指针的地址,否则形参的改变不影响实参)。

②要找到尾节点(该节点的next指向NULL)。

③需要malloc一个新节点(初始化的时侯将其next指向NULL)。

        

代码实现:

        

3.3头插

注意:

①需改变头指针(接口要传二级指针)。

②创建个新节点。

代码实现:

                        

3.4尾删

注意以下情况:

①空链表不可能尾删(assert断言)。

②只含一个节点的链表(需改变头指针,同时也要free要删除的节点)。

③含多个节点的链表(找尾节点)。

代码实现及其示意图:

3.5头删

注意:

①进行头删的可能是空链表。(assert断言一下)

②该链表含多个节点。

代码实现及其示意图:

3.6查找数据

通过编译链表,查找要找的值,找到了返回该节点,否则返回空指针。

代码实现:

        ​​​​​​​        ​​​​​​​        

3.7pos(链表节点)位置后面插入

为什么选择是在pos位置的后面插入,而不是前面插入呢?

由于这个是单向链表,如若我们在pos的前面插入,我们就需要编译一遍链表找到pos前面的节点在进行链接。相比在pos的后面插入效率低下

应考虑一下问题:

①pos不能传NULL。

②pos位置是链表的最后一个节点。(相当于尾插)

代码实现及其示意图: 
   

3.8pos(链表节点)位置后面删除

为什么不是删除pos节点的位置呢?

如若选择删除pos节点,我们需要编译一遍链表保存pos节点的前一个节点,这相比在pos位置后面删除就大大的降低了效率。

注意:

①pos不能是NULL。

②pos的next不能为NULL。

代码实现及示意图:

3.9链表打印

用于观察链表的数据。

代码实现:

        ​​​​​​​        ​​​​​​​        

3.10链表销毁

使用完链表时应进行销毁,否则会出现内存泄漏。

代码实现:

             

4.带头双向循环链表的实现(增删查改)

带头:头节点的数据无效,只是充当链表的头。

相比单链表其接口(增删查找)的实现比较简单。

4.1结构体设定

                        

4.2初始化

一开始链表只有一个头节点,需要将其next和prev指针都指向自己。

注意:由于是带头,所以需要malloc个头节点(里面存放的数据没有要求)。

代码实现及其示意图:

4.3判断是否为空

如果该链表的头节点的next指针指向自己则是空链表。

代码实现:

        ​​​​​​​        ​​​​​​​        ​​

4.4尾插

由于是带头的链表,不用向单链表一样要考虑多种不同情况。

代码实现及其示意图:

4.5尾删

注意:要判断是否为空链表(不包含头节点)。

代码实现及其示意图:

4.6头插

由于是带头的链表,头插是插在头节点的后面。

代码实现及其示意图:

4.7头删

由于是带头的链表,删除头节点的下一个节点。

代码实现及其示意图:

4.8查找

编译链表,不包括头节点,找到了返回该节点,否则返回NULL。

注意:要判断是否为空链表。

代码实现:

        ​​​​​​​        ​​​​​​​        

4.9pos(节点)的前面插入

配合查找接口使用,这个函数的实现可以替代前面的尾插和头插。

注意:pos不能是NULL

代码实现及其示意图:

​​​​​​​

尾插:pos直接传head,由于是带头双向循环链表head->prev直接指向的就是尾节点,直接套用上面的插入函数。

        ​​​​​​​        ​​​​​​​        

头插:   pos传head->next,head->next指向的就是链表有效数据的头节点,直接套用插入函数。

                        

4.10删除pos位置

配合查找函数一起使用,此函数的实现能代替尾删和头删。

注意:pos不能传NULL。(assert断言一下)

代码实现及其示意图:

尾删:pos需传head->prev,由于是双向带头循环链表head->prev指向尾节点,直接套用该函数。

                        

头删:pos传head->next(链表有效数据的头节点)。

                        

4.11打印链表

该函数一般是便于学习,观察链表数据而设的。

代码实现:

        ​​​​​​​        ​​​​​​​        

4.12销毁

使用完链表时,应释放该链表的数据,将其所占用的内存还给操作系统,否则回出现内存泄漏。

代码实现:

5.顺序表与链表的区别

欢迎大家的指教和询问,我会进行反思和回答,有不足的希望大家可以指出,我会进行反思和修改。谢谢大家,觉得不错的可以点点赞。