带头双向循环链表原来这么简单?

news/2025/3/15 9:51:20/

☃️个人主页: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);

三.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;
}

3.2链表的初始化

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

void LTInit(ListNode** pphead)
{*pphead = BuyListNode(-1);(*pphead)->next = *pphead;(*pphead)->prev = *pphead;
}

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;
}

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

void LTPushFront(ListNode* phead, LTDataType x)
{ListNode* newnode = BuyListNode(x);ListNode* next = phead->next;newnode->next = next;newnode->prev = phead;phead->next = newnode;
}

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");
}

3.5链表的尾删和头删

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

bool LTEmpty(ListNode* phead)
{assert(phead);return phead->next == phead;
}

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

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);
}

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

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);
}

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;
}

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;
}

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

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

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);
}

5.结尾

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

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


http://www.ppmy.cn/news/65839.html

相关文章

[oeasy]python0050_动态类型_静态类型_编译_运行

动态类型_静态类型 回忆上次内容 上次了解了 帮助文档的 生成 开头的三引号注释 可以生成 帮助文档文档 可以写成网页 python3 本身 也有 在线的帮助手册 目前的程序 提高了 可读性 有什么方法 可以让程序 更可读么&#xff1f;&#x1f914; 变量名 首先 在变量名上想办…

LeetCode 452 用最少数量的箭引爆气球

题目&#xff1a; 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在…

Guitar Pro8.1最新中文版自动扒谱编写吉他谱 新功能讲解

Guitar Pro8是一款非常受欢迎的音乐制作软件&#xff0c;它可以帮助用户创建和编辑各种音乐曲谱。从其诞生以来就送专门为了编写吉他谱而研发迭代的。 尽管这款软件可能已经成为全球最受欢迎的吉他打谱软件&#xff0c;在编写吉他六线谱和乐队总谱中始终处于行业领先地位&…

实时数仓项目开发过程中发现的几个问题和优化点(数据接入)

1、属性值被截断的问题 在数据实时接入阶段&#xff0c;使用NIFI ExecuteScript组件生成增、改、删SQL语句&#xff0c;将SQL语句放到了attribute中(详见视频教程http://mp.weixin.qq.com/s?__bizMzIyNzkwNDE4Nw&mid2247486672&idx1&sn41793a61dc5f7ca6b6f9a34b4…

基于人工智能AI视频分析的智慧安监解决方案

方案背景 为了保证对园区环境风险进行有效识别&#xff0c;传统视频监控存在视频结构化利用率低的问题&#xff0c;在实际使用过程中&#xff0c;安全管理人员工作效率低下&#xff0c;依靠人工肉眼查看灵活度低&#xff0c;风险漏报概率高&#xff0c;出现异常情况跟踪不及时&…

Linux下的Tomcat的安装详解--值得一看

如有错误&#xff0c;敬请谅解&#xff01; 此文章仅为本人学习笔记&#xff0c;仅供参考&#xff0c;如有冒犯&#xff0c;请联系作者删除&#xff01;&#xff01; 目录 简述静态网页和动态网页的区别。 简述 Webl.0 和 Web2.0 的区别。 tomcat8的安装&#xff0c;配置服…

zeppos 开发工具模拟器 simulator 无法显示app

zeppos 开发工具模拟器 simulator 无法显示app 目录问题描述&#xff1a;simulator的 Apps 不显示 hello-world 工程解决方案 目录 问题描述&#xff1a;simulator的 Apps 不显示 hello-world 工程 已确认部分&#xff1a; 1.网卡驱动安装成功 2.simulator version:1.1.9 3.d…

国内又款智能AI聊天软件-科大讯飞星火模型

介绍 介绍 中国科大讯飞星火GPT聊天软件是一款基于自然语言处理技术的人工智能聊天机器人。它利用了大量的文本数据&#xff0c;通过深度学习模型进行训练&#xff0c;从而实现与用户的智能对话。讯飞星火GPT聊天软件能够理解用户输入的问题或指令&#xff0c;并根据预设的回答…