当前位置: 首页 > news >正文

C语言数据结构初阶(4)----带头双向循环链表

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

看到这里我们可能会产生一个想法:这个链表看起来好复杂的样子,是不是它的增删改查比单链表更难写呢?嘿嘿,还真的不是这样的,双向链表的增删改查是很好写的哦!😁😁

  1. 函数接口一览

初阶数据结构我们学习的一般都是增删改查这四种操作:

// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* next;struct ListNode* prev;
} ListNode;
// 创建返回链表的头结点.
ListNode* BuyListNode(LTDataType x);
// 双向链表销毁
void ListDestory(ListNode* phead);
//双向链表的初始化
ListNode* ListInit();
// 双向链表打印
void ListPrint(ListNode* phead);
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* phead);
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* phead);
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos);

2. 函数接口的实现

2.1 ListNode* BuyListNode(LTDataType x)

这个函数存在的意义和单链表中的BuyListNo的函数一样哈。因为在尾插,头插,指定位置插入都需要向堆区申请空间,开辟节点,所以我们把它封装成一个函数。参数x用来给新的节点赋初值。代表你要开辟节点的data是多少。

//开辟新的节点
ListNode* BuyNewNode(LTDataType x)
{//malloc新的节点ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));//开辟失败退出程序if (newNode == NULL){perror("BuyNewNode::malloc");exit(-1);}else{//初始化新的节点,然后返回newNode->data = x;newNode->prev = NULL;newNode->next = NULL;return newNode;}
}

2.2 void ListDestory(ListNode* phead)

因为链表的节点都是在堆区开辟的,所以在使用完链表后需要将其释放。释放空间的方式就是遍历整个链表,注意在释放一个节点时需要找到该节点的下一个节点,否则会导致内存泄漏!注意phead不可传入空指针,因此需要对phead进行断言。

这里的phead就是传说中的哨兵位的头结点,好处在写完整个双向链表后就能够和好的理解啦,哨兵位的头结点不存储任何数据的。你可以把他理解为一个很大的官,他是不需要亲自上战场的,负责指挥他的兵,有了他,他的兵能够更好的实现各种操作。或者你可以把他理解为王者荣耀中的辅助,有了他才能更好的取得游戏的胜利。
//销毁链表
void ListDestory(ListNode* phead)
{//断言pheadassert(phead);ListNode* cur = phead->next;//遍历链表while (cur != phead){//找到下一个节点ListNode* next = cur -> next;//释放节点free(cur);//记录下一个要释放的节点cur = next;}//释放哨兵位的头结点free(phead);phead = NULL;
}

2.3 ListNode* ListInit()

初始化链表的函数就是开辟一个节点,让他作为哨兵位的头结点。然后让他的next,prev都指向自己。好处的话还需写代码的时候自己体会的哦!这样做的目的其实就是为了是头插,头删等操作统一化!还记得单链表写头插,头删函数时需要对特殊情况进行处理的痛苦吧!

//链表的初始化
ListNode* ListInit()
{//因为哨兵位的头节点是不需要存储数据的,这个参数随便是啥都行ListNode* phead = BuyNewNode(0);//prev,next均指向自己phead->next = phead;phead->prev = phead;//返回哨兵位的头结点return phead;
}

2.4 void ListPrint(ListNode* phead)

这个函数的实现也是很简单的哦!我们只需要遍历链表然后打印数据即可!同样不允许传入空指针,不然会发生空指针的解引用哦!这个不同于我们前面写过的单链表,单链表传入空指针说明链表为空!但这个双向链表经过初始化后就有一个哨兵位的头结点了!链表为空说明phead ->next = phead,或者phead->prev = phead。双链表为空的情况:

//双向链表的打印
void ListPrint(ListNode* phead)
{//断言assert(phead);//如果是空链表可以打印一个空,判断空链表的方法上面降到了//这里的话空链表就不做任何打印了ListNode* cur = phead->next;while (cur != phead){//用cur遍历双链表打印数据printf("%d ", cur->data);cur = cur->next;}printf("\n");
}

2.5 void ListPushBack(ListNode* phead, LTDataType x)

还记得单链表尾插的痛苦嘛!需要对特殊情况进行处理,我们来看看谁双向链表需不需要对特殊情况进行处理!尾插肯定是要找到尾节点的撒,双链表找尾节点就直接phead->prev 就可以了!

当双向链表为空时:

尾节点:tail = phead->prev = phead 哈。

当双向链表不为空时:

同样地,找到尾节点 tail = phead->prev,用上面同样的方式画图!我们发现双链表不为空时也是找到尾节点,然后进行上面的四步操作!这就是双向链表,哨兵位的头结点,这样初始化链表的好处!

//双链表的尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);//调用接口申请节点即可ListNode* newNode = BuyNewNode(x);//找到尾节点ListNode* tail = phead->prev;//链接新的节点tail->next = newNode;newNode->prev = tail;newNode->next = phead;phead->prev = newNode;
}

2.6 void ListPopBack(ListNode* phead)

删除节点时,同单链表没有数据不允许删除!双向链表判断为空的条件就是:phead->next = phead或者phead->prev = prev,我们只需要暴力断言即可!尾删的时候有两种删除方式哈!

  1. 记录尾节点并且记录尾节点的前一个节点。这样做就比较方便的,删除和链接不需要有任何的顺序。

  1. 只记录尾节点,那么就需要有一定的顺序啦!假设找到的尾节点是tail,删除顺序:

1:tail->prev->next = phead。

2:phead->prev = tail->prev。

3:释放尾节点tail。

我个人是比较喜欢第一种的哈!

//双向链表的尾删
void ListPopBack(ListNode* phead)
{assert(phead);//空链表不允许删除数据assert(phead->next != phead);//找到两个节点ListNode* tail = phead->prev;ListNode* pTail = tail->prev;//链接pTail->next = phead;phead->prev = pTail;//释放free(tail);tail = NULL;
}

2.7 void ListPushFront(ListNode* phead, LTDataType x)

双向链表的头插也是比较简单的哈!也不需要考虑特殊情况!如果不理解可以画画图就能够理解啦!

//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newNode = BuyNewNode(x);//找到哨兵位的头结点的下一个节点ListNode* next = phead->next;//找到next节点就随便链接就行phead->next = newNode;newNode->next = next;newNode->prev = phead;next->prev = newNode;
}

2.8 void ListPopFront(ListNode* phead)

双向链表真的挺简单的,哈哈,不懂的话就画画图。

//双链表的头删
void ListPopFront(ListNode* phead)
{assert(phead);//链表没有数据不允许删除assert(phead->next != phead);//依然找到要删除的节点,以及他的下一个节点ListNode* delNode = phead->next;ListNode* next = delNode->next;//找到这个节点之后随便链接就行next->prev = phead;phead->next = next;free(delNode);delNode = NULL;
}

2.9 ListNode* ListFind(ListNode* phead, LTDataType x)

查找的函数和单链表的一模一样,遍历链表找到data值相同的节点即可!这个函数主要是配合指定位置删除和插入的函数使用的!

//双链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{asset(phead);//用cur遍历链表ListNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}//没找到返回空指针return NULL;
}

2.10 void ListInsert(ListNode* pos, LTDataType x)

这个函数是在指定节点的位置之前插入一个新的节点哈,pos位置的节点通过LIstFind函数找到。我们只需要找到pos位置之前的那个节点,然后与newNode进行连接即可!当然不用找到pos的前一个节点也行的!

//双链表指定位置之前插入新节点
void ListInsert(ListNode* pos, LTDataType x)
{//如果ListFind函数返回的空指针,pos就是空指针,需要断言assert(pos);//开辟新节点ListNode* newNode = BuyNewNode(x);//找到前一个节点ListNode* prev = pos->prev;//连接prev->next = newNode;newNode->prev = prev;newNode->next = pos;pos->prev = newNode;
}

2.11 void ListErase(ListNode* pos)

和指定位置之前的插入一样,ListErase函数也是需要ListFind函数配合使用的!我们只需要找到pos位置的前一个节点,后一个节点,然后连接即可!

//删除指定位置的节点
void ListErase(ListNode* pos)
{//如果ListFind函数返回的空指针是不允许删除的assert(pos);//找到前一个和后一个节点ListNode* prev = pos->prev, * next = pos->next;//链接prev->next = next;next->prev = prev;//释放节点free(pos);pos = NULL;
}

3. 一个小小的技巧

在我们有了指定位置之前插入和删除的函数之后!头插,头删,尾插,尾删都可以使用这两个函数来替代啦!

头插:就是在phead->next 位置之前插入。

头删:就是删除 phead->next位置的节点。

尾插:就是在phead的位置插入一个新节点。

尾删:就是删除phead->prev位置所在的节点。

有了这个小技巧20min手撕双向带头循环链表!

4. 顺序表链表总结

不同点

顺序表

链表

存储空间上

物理上一定连续

逻辑上连续,但物理上不一定

连续

随机访问

支持O(1)

不支持:O(N)

任意位置插入或者删除

元素

可能需要搬移元素,效率低

O(N)

只需修改指针指向

插入

动态顺序表,空间不够时需要

扩容

没有容量的概念

应用场景

元素高效存储+频繁访问

任意位置插入和删除频繁

缓存利用率

相关文章:

C语言数据结构初阶(4)----带头双向循环链表

我们先来看看带头双向循环链表的结构:看到这里我们可能会产生一个想法:这个链表看起来好复杂的样子,是不是它的增删改查比单链表更难写呢?嘿嘿,还真的不是这样的,双向链表的增删改查是很好写的哦&#xff0…...

原生javascript手写一个丝滑的轮播图

通过本文,你将学到: htmlcssjs 没错,就是html,css,js,现在是框架盛行的时代,所以很少会有人在意原生三件套,通过本文实现一个丝滑的轮播图,带你重温html,css和js基础知识。 为什么选用轮播图做示例&…...

【Linux】进程优先级(进程优先级 Linux下优先级 用top命令更改已存在进程的nice 其他概念 进程切换)

文章目录进程优先级Linux下优先级用top命令更改已存在进程的nice:其他概念进程切换进程优先级 我们作为使用者一般不关心优先级,它跟我们的调度器有很大的关系,调度器是为了跟均衡的调度进程。 什么叫做优先级? 优先级和权限是两…...

2016年chatGPT之父Altman与马斯克的深度对话(值得一看)

2016年9月,现今OpenAI CEO,ChatGPT之父,时任创投公司Y Combinator的总裁Sam Altman在特斯拉加州弗里蒙特工厂采访了埃隆马斯克。马斯克阐述了创建OpenAI的初衷,以及就他而言,对于未来最为重要的五件事。这是OpenAI的两…...

基于vscode开发vue3项目的详细步骤教程 3 前端路由vue-router

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 3、基于vscode开发vue项目的详细步骤教程_水w的博客-CSDN博客 4、基于vscode开发vue项目的详细步骤教程 2 第三方图标库FontAw…...

【C语言】每日刷题 —— 牛客语法篇(5)

前言 大家好,继续更新专栏 c_牛客,不出意外的话每天更新十道题,难度也是从易到难,自己复习的同时也希望能帮助到大家,题目答案会根据我所学到的知识提供最优解。 🏡个人主页:悲伤的猪大肠9的博…...

操作系统(2.1)--进程的描述与控制

目录 一、前驱图和程序执行 1.前驱图 2.程序顺序执行 2.1 程序的顺序执行 2.2 程序顺序执行时的特征 3. 程序并发执行 3.1程序的并发执行 3.2 程序并发执行时的特征 一、前驱图和程序执行 1.前驱图 前趋图:是一个有向无循环图,用于描述进程之间执行的前后…...

JAVA查看动态代理类

JAVA查看代理类 1. 代理类 class 生成 System.setProperty // jdk8及之前的设置System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// or System.getProperties().put("sun.misc.ProxyGenerator.saveGenerated…...

Chapter2 : SpringBoot配置

尚硅谷SpringBoot顶尖教程 1. 全局配置文件 SpringBoot使用一个全局的配置文件 application.properties 或者 application.yml ,该配置文件放在src/main/resources目录或者类路径/config目录下面, 可以用来修改SpringBoot自动配置的默认值。 yml是YA…...

手撕单链表练习

Topic 1:LeetCode——203. 移除链表元素203. 移除链表元素 - 力扣(LeetCode)移除链表中的数字6操作很简单,我们只需要把2的指向地址修改就好了,原来的指向地址是6现在改为3这个思路是完全正确的,但是在链表…...

Kubuntu安装教程

文章目录Kubuntu介绍下载Kubuntu在VMware虚拟机中安装Kubuntu1. 点击“创建新的虚拟机”2. 选择“自定义(高级)”3. 按照下图所示进行设置设置网络4. 点击“自定义硬件”5. 开启虚拟机6. 进入安装界面,选择中文,之后点击“安装Kub…...

[蓝桥杯] 树状数组与线段树问题(C/C++)

文章目录 一、动态求连续区间和 1、1 题目描述 1、2 题解关键思路与解答 二、数星星 2、1 题目描述 2、2 题解关键思路与解答 三、数列区间最大值 3、1 题目描述 3、2 题解关键思路与解答 标题:树状数组与线段树问题 作者:Ggggggtm 寄语:与其…...

Matlab-Loma Prieta 地震分析

此示例说明如何将带时间戳的地震数据存储在时间表中以及如何使用时间表函数来分析和可视化数据。 加载地震数据 示例文件quake.mat包含 1989 年 10 月 17 日圣克鲁斯山脉 Loma Prieta 地震的 200 Hz 数据。这些数据由加州大学圣克鲁斯分校查尔斯F里希特地震实验室的 Joel Yelli…...

Spring Boot全局异常处理

使用注解方式处理全局异常使用 ControllerAdvice (RestControllerAdvice) 配合 ExceptionHandler适用于返回数据的请求(一般是RESTful接口规范下的JSON报文)package com.example.exception;import org.slf4j.Logger; import org.s…...

websocket每隔5秒给服务端send一次信息

websocket轮询每隔5秒给服务端send一次信息,主要功能点如下:socket 采用了定时器 setInterval() 需要清除定时器否则会报错监听了突然关闭浏览器窗口,destroyed里面直接监听 window.removeEventListener("beforeu…...

2023年中职网络安全——SQL注入测试(PL)解析

SQL注入测试(PL) 任务环境说明: 服务器场景:Server2312服务器场景操作系统:未知(关闭链接)已知靶机存在网站系统,使用Nmap工具扫描靶机端口,并将网站服务的端口号作为Flag(形式:Flag字符串)值提交。访问网站/admin/pinglun.asp页面,此页面存在SQL注入漏洞,使用排…...

利用蜜罐捕捉攻击实验(31)

预备知识 1、蜜罐的含义和作用 蜜罐(Honeypot)是一种在互联网上运行的计算机系统。它是专门为吸引并诱骗那些试图非法闯入他人计算机系统的人(如电脑黑客)而设计的,蜜罐系统是一个包含漏洞的诱骗系统,它通过模拟一个或多个易受攻击的主机&#xff…...

PyTorch深度学习实战 | 自然语言处理与强化学习

PyTorch是当前主流深度学习框架之一,其设计追求最少的封装、最直观的设计,其简洁优美的特性使得PyTorch代码更易理解,对新手非常友好。本文主要介绍深度学习领域中自然语言处理与强化学习部分。自然语言区别于计算机所使用的机器语言和程序语…...

测牛学堂:接口测试基础理论和工具的使用

接口测试流程总结 1 需求分析,看产品经理的需求文档 2 接口文档解析,开发编写的api接口文档 3 设计测试用例 4脚本开发 5 执行及缺陷跟踪 6 生成测试报告 7接口自动化持续集成 测试解析接口文档 接口文档,又称为api文档,是由后…...

定长内存池的实现

解决的是固定大小的内存申请释放需求&#xff1a; 性能达到极致不考虑内存碎片问题(统一使用自由链表管理还回来的空间) 为了避免命名污染&#xff0c;不要直接using namespace std;只展开常用的。 #include <iostream> using std::cout; using std::endl;申请空间时有…...

三更草堂springSecurity的学习

源码地址&#xff1a;学习springSecurity (gitee.com) git&#xff1a;https://gitee.com/misszyg/spring-security.git 一&#xff0c;认证流程 1&#xff0c;经过UsernamePasswordAuthenticationFilter &#xff08;1&#xff09;传入了用户的账号&#xff0c;密码 源码&a…...

【C语言】指针的深度理解(一)

前言 我们已经了解了指针的概念&#xff0c;一是指针变量是用来存放地址的&#xff0c;每个地址都对应着唯一的内存空间。二是指针的大小是固定的4或8个字节&#xff08;取决于操作系统&#xff0c;32位的占4个字节&#xff0c;64位的占8个字节&#xff09;。三是指针是有类型…...

Kafka最佳实践

前言 Kafka 最佳实践&#xff0c;涉及 典型使用场景Kafka 使用的最佳实践 Kafka 典型使用场景 Data Streaming Kafka 能够对接到 Spark、Flink、Flume 等多个主流的流数据处理技术。利用 Kafka 高吞吐量的特点&#xff0c;客户可以通过 Kafka 建立传输通道&#xff0c;把应…...

入门教程: 认识 React用于构建用户界面的 JavaScript 库

课前准备 我们将会在这个教程中开发一个小游戏。你可能并不打算做游戏开发,然后就直接跳过了这个教程——但是不妨尝试一下!你将在该教程中学到关于构建 React 应用的基础知识,掌握这些知识后,你将会对 React 有更加深刻的理解。 这篇教程分为以下几个部分: 环境准备是学…...

极紫外光源高次谐波发生腔不同区域真空度精密控制解决方案

摘要&#xff1a;在高次谐波发生器中一般包含两个不同真空区域&#xff0c;一个是1~100Torr绝压范围的气池内部的低真空区域&#xff0c;一个是高阶谐波光路上的绝压为0.001Pa量级的高真空区域。本文针对此两个区域的真空度控制提出了相应的解决方案&#xff0c;特别是详细介绍…...

「Vue面试题」在vue中为什么data属性是一个函数而不是一个对象

文章目录一、实例和组件定义data的区别二、组件data定义函数与对象的区别三、原理分析四、结论一、实例和组件定义data的区别 vue实例的时候定义data属性既可以是一个对象&#xff0c;也可以是一个函数 const app new Vue({el:"#app",// 对象格式data:{foo:"…...

如何使用 ChatGPT 编写 SQL JOIN 查询

通过清晰的示例和解释&#xff0c;本文展示了 ChatGPT 如何简化和简化创建复杂 MySQL 查询的过程&#xff0c;使用户更容易与数据库交互并检索他们需要的数据。无论您是初学者还是经验丰富的开发人员&#xff0c;本文都提供了有关如何利用 ChatGPT 来增强您的 MySQL 查询编写技…...

vue2+elementUI完成添加学生删除学生案列

效果图&#xff1a; 点击添加学生按钮&#xff0c;弹出Dialog,收集用户信息&#xff1a; el-table中自定义复选框&#xff0c;选中一行&#xff0c;可以点击删除 代码区域&#xff1a;就一个HTML文件 <!DOCTYPE html> <html lang"en"> <head>&…...

对void的深度理解

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; void前言一、 void 关键字二、 void修饰函数返回值和参数三、void指针3.1void * 定义的…...

哪款游戏蓝牙耳机好用?好用的游戏蓝牙耳机推荐

现在&#xff0c;不少人喜欢戴蓝牙耳机玩游戏&#xff0c;而在戴蓝牙耳机玩游戏时难免会产生音画不同步的问题。现在越来越多的蓝牙耳机支持游戏模式&#xff0c;那么&#xff0c;哪款游戏蓝牙耳机好用&#xff1f;接下来&#xff0c;我来给大家推荐几款好用的游戏蓝牙耳机&…...

赣州建设信息网/购买seo关键词排名优化官网

Objects类是一个提供对象基础操作的工具类&#xff0c;其提供的方法包括null-safe或tolerant-safe的对象hashcode计算&#xff0c;toString和比较等。所在路径&#xff1a;javautilObjects.javaObjects类方法列表一、构造器Objects类被final修饰&#xff0c;不能被继承。其构造…...

企业做网站怎么做/百度网页版进入

一.CommitFailedException CommitFailedException&#xff1a;Consumer客户端在提交位移时出现了错误或异常&#xff0c;而且还是不可恢复的严重异常。 二.产生CommitFailedException的原因&#xff1a; 本次提交位移失败&#xff0c;原因是消费者开启Rebalance过程&#xf…...

网站开发用什么字体一般/如何做seo优化

biti大师链接:http://biti_rainy.itpub.net/来自 “ ITPUB博客 ” &#xff0c;链接&#xff1a;http://blog.itpub.net/39335/viewspace-351665/&#xff0c;如需转载&#xff0c;请注明出处&#xff0c;否则将追究法律责任。 转载于:http://blog.itpub.net/39335/viewspace-…...

php动态网站开发论文/什么是百度竞价推广

转自&#xff1a;https://blog.csdn.net/paincupid/article/details/49924299 经常会接触到VO&#xff0c;DO&#xff0c;DTO的概念&#xff0c;本文从领域建模中的实体划分和项目中的实际应用情况两个角度&#xff0c;对这几个概念进行简析。 得出的主要结论是&#xff1a;在项…...

wordpress视频无法播放视频教程/市场营销策划方案案例

Scala和Groovy都是基于JVM的语言&#xff0c;相比Java都有更加简明的语法和丰富的表达能力。对于那些既想不脱离开JVM又想避免Java繁琐的语句的开发人员来说&#xff0c;Scala和Groovy都是不错的选择。可是选择哪一个才能在未来发展过程中取得先机呢&#xff1f;哪一个是未来发…...

快速建设网站视频教程/网络营销的营销理念

JDK的动态代理机制只能代理实现了接口的类&#xff0c;而不能实现接口的类就不能实现JDK的动态代理&#xff0c;cglib是针对类来实现代理的&#xff0c;他的原理是对指定的目标类生成一个子类&#xff0c;并覆盖其中方法实现增强&#xff0c;但因为采用的是继承&#xff0c;所以…...