深圳幻海软件技术有限公司 欢迎您!

【数据结构】带头双向循环链表的实现

2023-05-23

☃️个人主页:fighting小泽🌸作者简介:目前正在学习C语言和数据结构🌼博客专栏:数据结构🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪🏻文章目录前言一.带头双向循环链表的实现二.List.h三.List.c3.1创建一个新节点3.2链表的初始化3.3链表的尾插和头插3.4链表的打印3.

☃️个人主页:fighting小泽
🌸作者简介:目前正在学习C语言和数据结构
🌼博客专栏:数据结构
🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪🏻

文章目录

  • 前言
  • 一.带头双向循环链表的实现
  • 二.List.h
  • 三.List.c
    • 3.1创建一个新节点
    • 3.2链表的初始化
    • 3.3链表的尾插和头插
    • 3.4链表的打印
    • 3.5链表的尾删和头删
    • 3.6查找某个节点
    • 3.7链表的定向插入和删除
    • 3.8链表的销毁
  • 5.结尾

前言

虽然链表的结构有很多种,但我们实际中最常用的还是无头单向不循环链表和带头双向循环链表。

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

在上一节我们进行了单链表的实现,这次我们就实现一下看似困难,但实现起来却很爽的带头双向循环链表。

一.带头双向循环链表的实现

我们先看一看带头双向循环链表的结构

我们发现链表的每一个节点都存在两个指针,一个指向它的下一个节点,一个指向它的上一个节点。而头节点的前一个指针指向了最后一个节点,最后一个节点的指针指向了头节点,构成了循环结构。当我们想进行找尾操作的时候就不用遍历链表了,是不是很神奇。

接下来我们就实现一下带头双向循环链表。

二.List.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataType;

typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;

bool LTEmpty(ListNode* phead);

void LTInit(ListNode** pphead);

void LTPushBack(ListNode* phead, LTDataType x);

void LTPopBack(ListNode* phead);

void LTPushFront(ListNode* phead, LTDataType x);

void LTPopFront(ListNode* phead);

void LTPrint(ListNode* phead);

ListNode* LTFind(ListNode* phead, LTDataType x);

void LTInsert(ListNode* pos, LTDataType x);

void LTErase(ListNode* pos);

void LTDestroy(ListNode* phead);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

三.List.c

3.1创建一个新节点

由于我们进行链表的尾插头插时需要创建新的节点,并给它赋一个想要的值。所以我们可以写一个函数来创建新节点,然后返回这个节点的地址就可以了。

ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev= NULL;
return newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.2链表的初始化

开始我们会创建一个NULL指针,并把它初始化为头节点。由于它是一个循环链表,所以头节点的两个指针都指向它自己。又因为我们想要修改的是一个指针,所以我们需要传头节点的地址,即2级指针。

void LTInit(ListNode** pphead)
{
*pphead = BuyListNode(-1);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.3链表的尾插和头插

要进行尾插操作,只需要创建一个新节点,通过头节点找到尾节点,将新节点插在这两个节点之间就可以了。

void LTPushBack(ListNode* phead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
phead->prev->next = newnode;
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev = newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

进行头插操作,也是创建一个新节点,通过头节点找到它的下一个节点,把新节点插在这两个节点之间。

void LTPushFront(ListNode* phead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* next = phead->next;
newnode->next = next;
newnode->prev = phead;
phead->next = newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.4链表的打印

  • 单链表进行打印的时候是不是遍历一遍链表,打印节点的每一个值,直到遇到NULL指针停止?但是我们的带头双向循环链表是一直循环的,没有NULL指针。那我们该怎么办呢?
  • 其实也很简单,我们要定义一个cur为当前的节点,从链表的第一个节点开始遍历,每次打印节点的数据并将cur指向下一个节点,如果cur 等于头节点,则停止打印。
void LTPrint(ListNode* phead)
{
printf("guard<=>");
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.5链表的尾删和头删

由于链表只有一个头节点的时候是不能删除的,所以我们可以写一个函数来判断一下

bool LTEmpty(ListNode* phead)
{
assert(phead);

return phead->next == phead;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

尾删我们只需要通过头节点找到尾节点和尾节点的前一个节点,将头节点和尾节点的前一个节点链接起来,再把尾节点释放掉就可以了。

void LTPopBack(ListNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));

ListNode* tail = phead->prev;
ListNode* tailprev = tail->prev;

tailprev->next = phead;
phead->prev = tailprev;

free(tail);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

头删也是一样的道理,将头节点和头节点下一个节点的下一个节点链接起来,再把头节点的下一个释放掉就可以了。

void LTPopFront(ListNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));

ListNode* first = phead->next;
ListNode* second = first->next;

phead->next = second;
second->prev = phead;
free(first);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.6查找某个节点

  • 我们也是通过遍历链表来查找节点,如果节点存在就返回节点的地址,不存在就返回NULL指针。
  • 通过找到某个节点的地址,我们可以直接对它进行插入,删除操作。
ListNode* LTFind(ListNode* phead, LTDataType x)
{
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}

return NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.7链表的定向插入和删除

通过找到的节点地址,直接在它前面插入一个节点即可。

void LTInsert(ListNode* pos, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

通过找到的节点地址,将它的前一个节点和后一个节点链接起来,然后再把该节点释放掉即可

void LTErase(ListNode* pos)
{
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;

free(pos);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.8链表的销毁

void LTDestroy(ListNode* phead)
{
ListNode* cur = phead->next;
ListNode* next = NULL;
while (cur != phead)
{
next = cur->next;
free(cur);

cur = next;
}
free(phead);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5.结尾

这些就是我给大家分享的关于带头双向循环链表的知识啦,是不是很简单啊。希望我们都能有所收获!
先赞后看,养成习惯!!^ _ ^
码字不易,大家的支持就是我坚持下去的动力,点赞后不要忘了关注我哦!

如有错误,还请您批评改正(。ì _ í。)

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览46879 人正在系统学习中