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

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

前言:

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


目录

        前言:

                   概念:

         双链表的初始化

         双链表的判空

         双链表的打印

          双链表的头插

          双链表的尾插

           双链表头删

         双链表的尾删

          双链表的查找

         双链表在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;
}
总结:

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

相关文章:

  • spring-boot-maven-plugin 将spring打包成单个jar的工作原理
  • 25_04_30Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62
  • MySQL基础关键_002_DQL(一)
  • 湖北理元理律师事务所观察:民生债务问题的系统性解法
  • 【SpringBoot】基于mybatisPlus的博客管理系统(2)
  • 《操作系统真象还原》第十一章——用户进程
  • systemd和OpenSSH
  • (初探)强化学习路径规划的理论基础与代码实现
  • 介绍一下Files类的常用方法
  • verilog_testbench技巧
  • AI技术在当代互联网行业的崛起与重要性!
  • CUDA编程 - 如何使用 CUDA 流在 GPU 设备上并发执行多个内核 - 如何应用到自己的项目中 - concurrentKernels
  • 【影刀RPA实战案例】小红书商品数据采集
  • C++入门小馆: 模板
  • 【计算机视觉】语义分割:Segment Anything (SAM):通用图像分割的范式革命
  • C++ 与多技术融合的深度实践:从 AI 到硬件的全栈协同
  • 理想药用植物的特征综述-理想中药材”的系统定义-文献精读125
  • 【分享】变声器大师[特殊字符]乔碧萝同款变声[特殊字符]游戏变声[特殊字符]
  • 基于Q学习的2048游戏智能体:制作一个自己会玩游戏的智能体
  • rk3568 A/B系统 OAT升级 实践
  • 见证历史与未来共舞:上海西岸“蝶变共生”对话讲坛圆满举行
  • 魔都眼|静安光影派对五一启幕:苏河湾看徐悲鸿艺术画作
  • 160名老人报旅行团被扔服务区?张家界官方通报
  • 白云山一季度营收净利双降,此前称今年将挖掘盘活自身资源
  • 浙江官宣:五一假期,没电、没气、没油车辆全部免费拖离高速
  • 言短意长|新能源领军者密集捐赠母校