在本次的博客当中我们来向大家介绍一下双向循环链表,在介绍双向循环链表之前我们需要先来了解一下所有的链表的种类。
我们的链表大致分为八种:单向链表,双向链表,带头结点的链表,不带头结点的链表,循环链表,不循环的链表。经过组合一共分为八种。(单向不带头节点不循环的链表,双向带头结点循环的链表......)
我们之前实现过一个单项链表。单项链表是我们在题目中最常见的题目类型,但是他不是最实用的,因为我们想要在特定的节点前方插入一个数据的时候就需要从头开始遍历,找到我们目标节点的前一个位置,之后才可以进行插入等操作。但是对于双向链表来说,我们就不需要从头开始进行遍历了,我们结构体当中会自动存储一个指向前一个节点的指针,便于我们进行操作。
双向带头循环链表逻辑结构如下:
我们主要需要实现的链表当中的函数如下:
在准备函数的书写的之前我们要像我们编写单项链表的一样,构建一个结构体,作为我们每一个节点的模板,便于后面使用。所示代码如下:
🌵ListCreate函数
在我们第一次构建循环链表的时候,最重要的就是创建头节点,我们需要梳理好我们的逻辑,当我们的链表当中只存在我们的头节点的时候,(也就代表我们我们链表当中的数据项为0)我们同样需要构建一个循环链表,只不过我们的循环当中只存在一个头节点。就像是下面的结构所示:
我们要完成的是我们对于头节点的初始化的函数。所示代码如下:
首先我们向堆区请求分配一个结构体大小的空间,之后将我们的头节点的指针更改为我们开辟出来的空间的地址,之后将我们节点当中的数据初始化为 -1 并将我们的前一个指针和后一个指针均指向我们该节点。需要注意的是,我们有两种改变头节点的方式,第一种是在我们的参数当中传入一个二级指针,直接更改我们头节点的地址的值。第二种是让我们的函数返回处理好的地址,并在主函数当中使用phead进行接收。
🌵ListPushBack函数
在完成初始化节点之后我们就可以开始向我们的链表当中尝试着存储数据了,我们先来构建一个尾插函数,代码如下:
首先我们开辟一个新的节点进行接收我们创建的新的节点。
之后我们想要将我们的新节点连接进入我们的链表当中,我们会发现想要在链表的尾部插入其实和在我们的phead之前插入是一样的道理,因为我们每一次遍历链表都是从phead开始的,所以最后遍历到的节点只需要在我们头节点的前方即可。我们将节点链接进入我们的链表当中:
就像是我们上图中所示的那样,我们需要更改四个指针的链接即可。但是我们需要注意的是我们需要提前创建一个结构体指针将我们原链表当中的尾节点保存起来,不然当我们更改了phead头指针之后就会丢失我们尾节点的地址。
🌵ListPrint函数
在完成了数据的插入之后我们仅仅在理论上完成了循环链表的构建但是我们要想查看就还需要构建一个打印函数。
对于打印函数我们依旧是遍历整个链表,需要我们注意的也就是循环的结束条件了,循环链表在我们查找到最后一个节点的时候下一个节点就会回到我们的头节点,所以我们只需要判断我们节点的下一项是否为头节点即可。结合我们尾插函数进行代码的检测:
🌵ListPushFront函数
当我们完成尾插函数之后和我们尾插函数逻辑相类似的就是我们的头插函数,甚至我们还可以将我们的尾插函数直接复制粘贴到我们的头插函数当中,只需要修改部分逻辑即可:
唯一不同的就是我们对于链表的头插函数需要将我们新开辟的节点插入到我们我们头节点的后面
在这里我们需要创建一个结构体指针变量保存我们原来链表当中phead节点的下一个节点的地址,原理和我们的尾插函数相同。运行效果如下:
🌵ListPopFront函数
在构建完我们的节点增加的函数之后就可以构建我们的节点删除的函数了:
首先我们需要判断我们链表当中是否存在存储数据的节点,只有存储数据的节点的时候我们才需要进行删除否则就直接返回即可。否则再删除的话就有可能破坏循环链表的结构。我们使用一个结构体指针存储我们第二个数据节点之后就可以删除第一个节点了,之后再进行略微的代码的修改即可。 我们在这里需要更改两个指针链接即可,就像是上图中所示的那样,我们不需要管拆下来的节点的指针指向,因为我们将其释放之后就会将其置为空,不在对其进行使用。 代码测试如下:
🌵ListPopBack函数
和我们头删函数类似的是我们的尾删函数,我们同样可以将我们的代码复制粘贴一份进行复用:
同样的我们需要先对链表进行判空操作,之后将我们链表当中phead的前一个节点按照同样的逻辑进行删除即可。运行结果如下:
🌵 ListFind函数
之后再来构建我们的查找函数。
我们第一步需要做的同样是对链表进行判空,如果链表当中为空就可以直接返回NULL,并提示无法进行查找。否则就进行链表的遍历,逻辑和我们打印函数类似,直到我们我们查找的节点下一个指向头节点即可结束,如果在遍历途中就可以返回节点的地址,如果没有返回并且循环结束就代表没有找到,就返回空指针。测试效果如下:
🌵ListInsert函数
有了查找函数之后我们的在指定位置插入删除的函数构建起来就方便多了。还记得我们在单链表的构建过程当中,在我们指定节点的位置前面插入新节点的时候,需要进行遍历,会造成效率上面的问题。代码如下:
对于链表节点的插入逻辑和我们的头插尾插相似。 测试效果如下:
🌵ListErase函数
之后删除我们指定位置的节点,代码编写逻辑和我们的头删类似:
因为我们向函数的形参部分传入的是一个指针,我们在查找时候接收到的指针代表着这个元素已经存在,那么我们只需要直接使用即可,无需考虑链表当中是否为空的情况。运行效果如下:
🌵ListDestory函数
最后我们在使用完我们的链表之后需要将我们的每一个节点都返回给我们的操作系统,否则就会存在内存泄露的危险,因此我们最后还需要创建一个销毁函数。代码如下:
我们需要提前创建一个结构体变量接收我们下一步骤当中想要释放的节点,循环释放节点的逻辑和我们遍历结构体相似,我们依旧将我们的循环结束条件定义为判断下一个地址是否为phead。文章来源:https://www.toymoban.com/news/detail-428560.html
在编写完上述代码之后我们的循环链表也就正式编写结束了,感谢您的观看,下次博客再见。 文章来源地址https://www.toymoban.com/news/detail-428560.html
到了这里,关于双向链表的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!