【数据结构】--- 双向链表的增删查改

前言:

      经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽


目录

        前言:

                   概念:

         双链表的初始化

         双链表的判空

         双链表的打印

          双链表的头插

          双链表的尾插

           双链表头删

         双链表的尾删

          双链表的查找

         双链表在pos位置前进行插入

        双链表删除pos位置

         双链表的销毁

                                    检查:

                                   完整代码:

                                        dlist.h

                        ​​​​​​​        ​​​​​​​        dlist.c

                ​​​​​​​        ​​​​​​​        ​​​​​​​        test.c                                                                                                   

总结:


概念:

      双向链表的英文是 Doubly Linked List。双向链表是一种链表数据结构,其中每个节点包含三个部分:前驱指针、数据域和后继指针。前驱指针指向前一个节点,后继指针指向下一个节点。

双向链表的节点结构

双向链表的节点结构包括三个部分:

  1. 前驱指针域 (_prev):用于存放指向上一个节点的指针。

  2. 数据域 (_data):用于存储节点的数据元素。

  3. 后继指针域 (_next):用于存放指向下一个节点的指针。

typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* _next;struct ListNode* _prev;
}ListNode;

双链表的基本操作

       双链表是一种数据结构,它的每个节点除了存储数据外,还有两个指针,分别指向前一个节点和后一个节点。这种结构使得双链表在进行插入和删除操作时更为高效,因为可以直接访问任何节点的前驱和后继节点。

双链表的初始化

      在开始对我们的双链表进行增删查改前,我们先要对链表进行初始化,和单链表差不多,需要一个创建新节点的函数,不同的是新节点多了一个前驱,也需要置空

      然后创建链表的头节点,头节点的值我们取-1,当头节点创建成功的时候,我们将其前驱和后继指向自己

//创建新节点
ListNode* BuySListNode(LTDataType x) {ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL) {perror("malloc fail");return NULL;}newnode->_data = x;newnode->_next = NULL;newnode->_prev= NULL;return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate() {ListNode*head= BuySListNode(-1);if (head != NULL) {head->_prev = head;head->_next = head;}return head;
}
双链表的判空
bool ListEmptyLTNode(ListNode* phead)
{assert(phead);/*链表返回只剩头节点(链表已经被删空)为真否则为假*/return phead->_next == phead;
}
双链表的打印

遍历链表

// 双向链表打印
void ListPrint(ListNode* pHead) {assert(pHead);ListNode* start = pHead->_next;while (start!=pHead) {printf("%d<=>", start->_data);start = start->_next;}printf("\n");
}
双链表的头插

       创建一个新节点,保存头节点的后继,令其为ne,让新节点的下一个指向ne,ne的前驱指向新节点,新节点的前驱指向头节点,头节点的后继指向新节点

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* newnode = BuySListNode(x);ListNode* ne = pHead->_next;newnode->_next = ne;ne->_prev = newnode;newnode->_prev = pHead;pHead->_next = newnode;
}
双链表的尾插
// 在链表尾部插入新节点
void ListPushBack(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* newnode = BuySListNode(x);  // 创建新节点// 调整指针将新节点接入链表尾部ListNode* pre = pHead->_prev;  // 原尾节点pre->_next = newnode;          // 原尾节点的next指向新节点newnode->_prev = pre;          // 新节点的prev指向原尾节点newnode->_next = pHead;        // 新节点的next指向头节点pHead->_prev = newnode;        // 头节点的prev指向新尾节点
}

双链表头删

先看看链表是否为空,空链表不能进行尾删

      然后保存头节点的后继的后继,让头节点的后继指向头节点的后继的后继,头节点的后继的后继的前驱指向头节点

// 双向链表头删
void ListPopFront(ListNode* pHead) {assert(pHead);if (!ListEmptyLTNode(pHead)) {ListNode* check = pHead->_next->_next;ListNode* tmp = pHead->_next;pHead->_next = check;check->_prev = pHead;free(tmp);}
}
双链表的尾删

先看看链表是否为空,空链表不能进行尾删

      然后保存头节点的前驱的前驱,让头节点的前驱指向头节点的前驱的前驱,头节点的前驱的前驱的后继指向头节点

// 双向链表尾删
void ListPopBack(ListNode* pHead) {assert(pHead);if (!ListEmptyLTNode(pHead)) {ListNode* tmp = pHead->_prev;ListNode* pre = pHead->_prev->_prev;pre->_next = pHead;pHead->_prev = pre;free(tmp);}}
双链表的查找

遍历链表

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* start = pHead->_next;while (start != pHead) {if (start->_data == x)return start;start = start->_next;}return NULL;
}
双链表在pos位置前进行插入

       先判断pos是否有效,创建一个新节点,然后将pos位置的前驱保存下来,让新节点的前驱指向pos的前驱新节点的后继指向pos,pos的前驱指向新节点,pos前驱的后继指向新节点

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {assert(pos);ListNode* newnode = BuySListNode(x);ListNode* pre = pos->_prev;newnode->_next = pos;pos->_prev = newnode;pre->_next = newnode;newnode->_prev = pre;
}
双链表删除pos位置

       先判断pos是否有效,然后将pos位置的前驱和后继保存下来,让pos的前驱的后继指向pos的后继,pos后继的前驱指向pos的前驱

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {assert(pos);ListNode* ne = pos->_next;ListNode* pre = pos->_prev;ne->_prev = pre;pre->_next = ne;free(pos);
}
双链表的销毁

     先断言头节点有效,然后再创建一个start指针指向头节点的后继,当该指针不等于头节点时,不断遍历,先保存start指针的后继,然后释放掉start指针所指向的内存,再将原来保存的后继重新给到start指针,最后再释放头节点,需在外层置空指针,防止野指针问题。

// 双向链表销毁
void ListDestory(ListNode* pHead) {assert(pHead);ListNode* start = pHead->_next;while (start != pHead) {ListNode* ne = start->_next;free(start);start = ne;}free(pHead);//pHead = NULL;
}
检查:
#include "dlist.h"int main() {ListNode* head = ListCreate(); // 创建链表头节点// 尾插入测试ListPushBack(head, 1);ListPushBack(head, 2);ListPushBack(head, 3);printf("链表内容(尾插入后):");ListPrint(head);// 头插入测试ListPushFront(head, 0);printf("链表内容(头插入后):");ListPrint(head);// 查找测试ListNode* found = ListFind(head, 2);if (found) {printf("找到节点:%d\n", found->_data);}else {printf("未找到节点\n");}// 删除头节点测试ListPopFront(head);printf("链表内容(头删后):");ListPrint(head);// 删除尾节点测试ListPopBack(head);printf("链表内容(尾删后):");ListPrint(head);// 在指定位置插入测试ListInsert(head->_next, 4); // 在第二个节点后插入printf("链表内容(插入4后):");ListPrint(head);// 删除指定节点测试ListErase(head->_next); // 删除第二个节点printf("链表内容(删除第二个节点后):");ListPrint(head);// 销毁链表ListDestory(head);// printf("链表内容(销毁后):");// ListPrint(head); // 应该输出空链表的状态
head=NULL;return 0;
}
完整代码:
dlist.h
#pragma once
#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;ListNode* BuySListNode(LTDataType x);
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
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);
bool ListEmptyLTNode(ListNode* phead);
dlist.c
#include "dlist.h"ListNode* BuySListNode(LTDataType x) {ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL) {perror("malloc fail");return NULL;}newnode->_data = x;newnode->_next = NULL;newnode->_prev= NULL;return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate() {ListNode*head= BuySListNode(-1);if (head != NULL) {head->_prev = head;head->_next = head;}return head;
}
// 双向链表销毁
void ListDestory(ListNode* pHead) {assert(pHead);ListNode* start = pHead->_next;while (start != pHead) {ListNode* ne = start->_next;free(start);start = ne;}free(pHead);//pHead = NULL;
}
bool ListEmptyLTNode(ListNode* phead)
{assert(phead);/*链表返回只剩头节点(链表已经被删空)为真否则为假*/return phead->_next == phead;
}
// 双向链表打印
void ListPrint(ListNode* pHead) {assert(pHead);ListNode* start = pHead->_next;while (start!=pHead) {printf("%d<=>", start->_data);start = start->_next;}printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* newnode = BuySListNode(x);ListNode* pre = pHead->_prev;pre->_next = newnode;newnode->_prev = pre;newnode->_next = pHead;pHead->_prev = newnode;}
// 双向链表尾删
void ListPopBack(ListNode* pHead) {assert(pHead);if (!ListEmptyLTNode(pHead)) {ListNode* tmp = pHead->_prev;ListNode* pre = pHead->_prev->_prev;pre->_next = pHead;pHead->_prev = pre;free(tmp);}}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* newnode = BuySListNode(x);ListNode* ne = pHead->_next;newnode->_next = ne;ne->_prev = newnode;newnode->_prev = pHead;pHead->_next = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead) {assert(pHead);ListNode* check = pHead->_next->_next;ListNode* tmp = pHead->_next;pHead->_next = check;check->_prev = pHead;free(tmp);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* start = pHead->_next;while (start != pHead) {if (start->_data == x)return start;start = start->_next;}return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {assert(pos);ListNode* newnode = BuySListNode(x);ListNode* pre = pos->_prev;newnode->_next = pos;pos->_prev = newnode;pre->_next = newnode;newnode->_prev = pre;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {assert(pos);ListNode* ne = pos->_next;ListNode* pre = pos->_prev;ne->_prev = pre;pre->_next = ne;free(pos);
}
test.c
#include "dlist.h"int main() {ListNode* head = ListCreate(); // 创建链表头节点// 尾插入测试ListPushBack(head, 1);ListPushBack(head, 2);ListPushBack(head, 3);printf("链表内容(尾插入后):");ListPrint(head);// 头插入测试ListPushFront(head, 0);printf("链表内容(头插入后):");ListPrint(head);// 查找测试ListNode* found = ListFind(head, 2);if (found) {printf("找到节点:%d\n", found->_data);}else {printf("未找到节点\n");}// 删除头节点测试ListPopFront(head);printf("链表内容(头删后):");ListPrint(head);// 删除尾节点测试ListPopBack(head);printf("链表内容(尾删后):");ListPrint(head);// 在指定位置插入测试ListInsert(head->_next, 4); // 在第二个节点后插入printf("链表内容(插入4后):");ListPrint(head);// 删除指定节点测试ListErase(head->_next); // 删除第二个节点printf("链表内容(删除第二个节点后):");ListPrint(head);// 销毁链表ListDestory(head);// printf("链表内容(销毁后):");// ListPrint(head); // 应该输出空链表的状态
head=NULL;return 0;
}
总结:

       本篇关于双链表的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78207.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu如何查看硬盘的使用情况,以及挂载情况。

在Ubuntu中查看硬盘使用情况及挂载情况&#xff0c;可通过以下命令实现&#xff1a; 一、查看硬盘使用情况 df -h 显示所有挂载文件系统的磁盘空间使用情况&#xff08;含总容量、已用空间、可用空间等&#xff09;&#xff0c;输出结果以易读格式&#xff08;如GB、MB&#x…

Github 2025-05-02Java开源项目日报 Top9

根据Github Trendings的统计,今日(2025-05-02统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9Android开源轻量级流媒体前端 创建周期:3158 天开发语言:Java协议类型:GNU General Public License v3.0Star数量:28641 个Fork数量…

linux学习——数据库API创建

一.API操作 1.int sqlite3_open(char *filename,sqlite3 **db) 功能&#xff1a;打开sqlite数据库 参数&#xff1a; filename:数据库文件路径 db:指向sqlite句柄的指针 &#xff08;splite3* db;&#xff09; 返回值…

Baklib内容中台落地实战指南

内容中台实施最佳路径 在构建企业级内容中台的实践中&#xff0c;架构设计与流程优化构成核心支撑框架。通过四库体系&#xff08;知识库、资源库、模板库、场景库&#xff09;的有机组合&#xff0c;企业可实现从知识沉淀到场景化应用的闭环管理。智能检索技术结合语义分析引…

【重走C++学习之路】26、类型转换

目录 一、C语言中的类型转换 二、C中的四个类型转换 2.1 static_cast 2.2 dynamic_cast 2.3 const_cast 2.4 reinterpret_cast 2.5 总结 结语 一、C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&a…

kotlin 过滤 filter 函数的作用和使用场景

1. filter 函数的作用 filter 是 Kotlin 集合操作中的一个高阶函数&#xff0c;用于根据指定条件从集合中筛选出符合条件的元素。 作用&#xff1a;遍历集合中的每个元素&#xff0c;并通过给定的 lambda 表达式判断是否保留该元素。返回值&#xff1a;一个新的集合&#xff…

安卓程序打包与发布

一 配置编译信息 二 创建密钥

LeetCode算法题 (移除链表元素)Day15!!!C/C++

https://leetcode.cn/problems/remove-linked-list-elements/description/ 一、题目分析 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 今天的题目非常好理解&#xff0c;也就是要删除…

Scrapy框架之【Scrapy-Redis】分布式爬虫详解

Scrapy-Redis 介绍 Scrapy-Redis 是一个基于 Redis 实现的 Scrapy 分布式爬虫组件。Scrapy 本身是一个强大的 Python爬虫框架&#xff0c;但它默认是单进程单线程的&#xff0c;在面对大规模数据抓取任务时效率不高。Scrapy-Redis 则解决了这一问题&#xff0c;它允许你将 Scra…

Gradio全解20——Streaming:流式传输的多媒体应用(3)——实时语音识别技术

Gradio全解20——Streaming&#xff1a;流式传输的多媒体应用&#xff08;3&#xff09;——实时语音识别技术 本篇摘要20. Streaming&#xff1a;流式传输的多媒体应用20.3 实时语音识别技术20.3.1 环境准备和开发步骤1. 环境准备2. ASR应用开发步骤&#xff08;基于Transform…

使用xlwings将两张顺序错乱的表格进行数据核对

有如下一个excel表&#xff0c;姓名列的内容相同&#xff0c;顺序不同&#xff1b;月薪有部分内容不同。 目的&#xff1a;要找出哪几条月薪不同。 通常的做法&#xff0c;要使用excel的高级筛选。 在此&#xff0c;使用xlwings实现&#xff0c;在不同的内容上涂色。 代码如…

2025大模型安全研究十大框架合集(10份)

2025大模型安全研究十大框架合集的详细介绍&#xff1a; Anthropic AI信任研究框架 Anthropic于2024年10月更新的《安全责任扩展政策》(RSP)&#xff0c;提出了一个灵活的动态AI风险治理框架。该框架规定当AI模型达到特定能力时&#xff0c;将自动升级安全措施&#xff0c;如…

Qt/C++开发监控GB28181系统/云台控制/获取预置位信息/添加删除调用预置位

一、前言 之前用onvif已经完美实现了设备的云台控制和预置位的功能&#xff0c;这个基础功能在监控系统中是使用频率很高的&#xff0c;所有gb28181肯定也提供了这样的功能&#xff0c;很多人以为是通过包含xml数据&#xff0c;对应节点指定对应的动作来实现&#xff0c;其实不…

第T8周:猫狗识别

● 语言环境&#xff1a;Python3.8.8 ● 编译器&#xff1a;Jupyter Lab ● 深度学习环境&#xff1a;TensorFlow2.4.1 猫狗识别 一、前期工作1. 设置GPU 二、数据预处理1. 加载数据2.再次检查数据3.配置数据集 三、构建VG-16网络四、编译五、训练模型六、模型评估七、预测八、…

主流微前端框架比较

主流微前端框架比较 以下表格列出了当前主流微前端框架的核心对比信息,包括基本介绍、核心特性、适用场景、技术栈兼容性、优缺点、社区维护情况和典型应用案例等: 框架基本介绍核心特性与机制适用场景技术栈兼容性优缺点社区维护情况典型应用案例qiankun蚂蚁金服推出的生产…

大学生入学审核系统设计与实现【基于SpringBoot + Vue 前后端分离技术】

一、项目概述 1.1 项目背景 随着高校的不断扩招&#xff0c;传统的入学审核管理模式已不能满足大规模学生数据的处理需求。人工管理不仅效率低下&#xff0c;还容易出现疏漏。本系统通过信息化手段&#xff0c;提升入学审核过程中的数据管理和审批效率。 1.2 系统目标 系统…

云计算-容器云-服务网格Bookinfo

服务网格&#xff1a;创建 Ingress Gateway 将 Bookinfo 应用部署到 default 命名空间下&#xff0c;请为 Bookinfo 应用创建一个网 关&#xff0c;使外部可以访问 Bookinfo 应用。 上传ServiceMesh.tar.gz包 [rootk8s-master-node1 ~]# tar -zxvf ServiceMesh.tar.gz [rootk…

Spring 分批处理 + 冷热数据分离:历史订单高效迁移与数据清理实战

在实际业务中&#xff0c;随着时间推移&#xff0c;订单量持续增长&#xff0c;若未及时进行数据治理&#xff0c;会造成数据库膨胀、查询缓慢、性能下降等问题。为了实现数据分层管理和系统高性能运行&#xff0c;我们在项目中采用了“冷热数据分离 分批迁移 数据清理”的综…

新手SEO优化核心步骤

内容概要 对于SEO新手而言&#xff0c;建立系统化的优化框架是突破入门瓶颈的关键。SEO的核心在于通过技术手段与内容策略的结合&#xff0c;提升网站在搜索引擎中的可见性与用户价值。具体而言&#xff0c;新手需优先掌握关键词研究&#xff0c;明确目标用户的搜索意图&#…

C++ 之 【list的简介、list 的构造函数、iterator、容量操作、元素访问、增删查改与迭代器失效】

目录 1.list的介绍 2.list的使用 2.1 构造函数 2.2 iterator 的使用 2.3 容量操作 2.4 元素访问 2.5 增删查改 2.5.1头插头删与尾插尾删 2.5.2 insert 、erase 函数 2.5.3 clear、swap函数 2.5.4 关于find函数 3.迭代器失效 1.list的介绍 (1)list的底层通常实现为带…