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

【数据结构】双向链表的实现

深入理解双向链表:从创建到核心操作的完整指南

双向链表是数据结构中极具代表性的链式结构,它解决了单链表 “单向遍历” 的局限,让数据操作更灵活。本文将带你从概念理解代码实现,彻底掌握双向链表的创建、初始化以及头插、尾插、头删、尾删等核心操作。

双向链表

  • 深入理解双向链表:从创建到核心操作的完整指南
    • 一、双向链表是什么?和单链表有何不同?
      • 结构对比:单链表 vs 双向链表
    • 二、双向链表的节点结构定义
    • 三、双向链表的初始化与创建
    • 四、核心操作 1:尾插(在链表尾部插入节点)
    • 五、核心操作 1:头插(在链表头部插入节点)
    • 六、核心操作 4:尾删(删除链表尾部节点)
    • 七、核心操作 3:头删(删除链表头部节点)
  • 八、查找节点(搭配指定位置添加和删除)
  • 九、指定位置尾插
  • 十、指定位置头插
  • 十一、指定位置删除

一、双向链表是什么?和单链表有何不同?

如果把单链表比作 “单向行驶的火车”(只能从车头到车尾),那双向链表就是 “双向行驶的高铁”—— 它的每个节点不仅能指向 “下一个节点”,还能指向 “前一个节点”。

结构对比:单链表 vs 双向链表

  • 单链表节点:仅包含 “数据域” 和 “指向下一节点的指针域”。
  • 双向链表节点:包含 “数据域”、“指向下一节点的指针域(next)”、“指向前一节点的指针域(prev)”。

这种结构让双向链表具备两大核心优势:

  • 双向遍历:既能从前往后找,也能从后往前找。
  • 插入 / 删除效率更高:无需像单链表那样遍历找前驱节点,通过prev指针可直接定位。

二、双向链表的节点结构定义

首先定义双向链表的节点结构,这是实现所有操作的基础:
头文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{LTDataType data;          //数据域:存储节点数据struct ListNode* next;    //前驱指针:指向前一个节点struct ListNode* prev;    //后驱指针:指向后一个节点
}ListNode;

三、双向链表的初始化与创建

初始化的目标是创建一个空链表,让头、尾指针都指向NULL,长度置为 0。
形式:

//初始化
void ListInit(ListNode** PPhead);

实现函数:

//申请节点
ListNode* LTBuyNode(LTDataType x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("maloc fail!");exit(1);}node->data = x;//双向链表的节点要自己指向自己node->next = node->prev = node;return node;
}//初始化
void ListInit(ListNode** PPhead)
{//给链表创建一个哨兵位*PPhead = LTBuyNode(-1);}

四、核心操作 1:尾插(在链表尾部插入节点)

尾插的逻辑是:新节点成为 “新尾”,原尾节点成为新节点的prev,新节点成为原尾节点的next
形式:

//尾删
void ListPopBack(ListNode* Phead);
//由于不能改变哨兵位,使用不用传入节点的地址,预防改变哨兵位

函数实现:

//尾删
void ListPopBack(ListNode* Phead)
{assert(Phead && Phead->next);#if 0//方案1://让被删除的上一个节点,指向头节点Phead->prev->prev->next = Phead;//指向好了,就把尾节点释放掉ListNode* scr = Phead->prev;free(scr);scr = NULL;//让头节点的尾指向被删除的上一个节点Phead->prev = Phead->prev->prev;
#endif#if 1//方案2://创建一个被删除节点的变量ListNode* del = Phead->prev;//Phead del->prev deldel->prev = Phead;Phead->next = del->prev;//释放掉删除的节点free(del);del = NULL;
#endif
}

五、核心操作 1:头插(在链表头部插入节点)

头插的逻辑是:新节点成为 “新头”,原头节点成为新节点的next,新节点成为原头节点的prev
形式:

//头插
void ListPushFront(ListNode* Phead, LTDataType x);

函数实现:

//头插
void ListPushFront(ListNode* Phead, LTDataType x)
{assert(Phead);ListNode* newnode = LTBuyNode(x);//与尾插的思维相同,画图分析newnode->next = Phead->next;newnode->prev = Phead;//需改变的节点:Phead newnode Phead->next;//两行代码不能完全交换Phead->next->prev = newnode;Phead->next = newnode;
}

六、核心操作 4:尾删(删除链表尾部节点)

尾删的逻辑是:将尾指针前移一位,同时断开原尾节点的prevnext,并释放内存。

//尾删
void ListPopBack(ListNode* Phead);

函数实现:

//尾删
void ListPopBack(ListNode* Phead)
{assert(Phead && Phead->next != Phead);#if 0//方案1://让被删除的上一个节点,指向头节点Phead->prev->prev->next = Phead;//指向好了,就把尾节点释放掉ListNode* scr = Phead->prev;free(scr);scr = NULL;//让头节点的尾指向被删除的上一个节点Phead->prev = Phead->prev->prev;
#endif#if 1//方案2://创建一个被删除节点的变量ListNode* del = Phead->prev;//Phead del->prev delPhead->prev = del->prev;del->prev->next = Phead;//释放掉删除的节点free(del);del = NULL;
#endif
}

七、核心操作 3:头删(删除链表头部节点)

头删的逻辑是:将头指针后移一位,同时断开原头节点的prevnext,并释放内存。
形式:

//头删
void ListPopFront(ListNode* Phead);

函数实现:

//头删
void ListPopFront(ListNode* Phead)
{assert(Phead && Phead->next != Phead);#if 0//让头节点指向被删除的下一个节点//1.必须先把被删除的下一个节点用指针保存起来//2.因为在释放内存时,空指针不能解引用ListNode* PheadNext = Phead->next->next;//手动释放被删除的空间//1.将第一个节点释放时,需要一个指针接收//2.因为在释放时,不用指针接收的地址释放,就会产生未初始化的指针解引用ListNode* scr = Phead->next;free(scr);scr = NULL;//让头节点指向被删除的下一个节点Phead->next = PheadNext;//让被删除的下一个节点,指向头节点Phead->next->next->prev = Phead;
#endif#if 1ListNode* del = Phead->next;//Phead del->next del//指向第二个节点Phead->next = del->next;//指向哨兵位del->next->prev = Phead;//手动释放删除的节点free(del);del = NULL;
#endif
}

八、查找节点(搭配指定位置添加和删除)

查找节点的逻辑是:循环遍历双向链表,如果节点中的数据等于要找的数据,就返回当前地址,否则返回NULL
形式:

//查找节点
ListNode* ListFind(ListNode* Phead, LTDataType x);

函数实现:

//查找节点
ListNode* ListFind(ListNode* Phead, LTDataType x)
{ListNode* pcur = Phead->next;while (pcur != Phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

九、指定位置尾插

指定位置尾插,无论插入的位置在哪都不会影响该结果,包括尾插也一样。函数实现可以查考尾插
形式:

//指定位置之后插入数据
void ListInsert(ListNode* pos, LTDataType x);

函数实现:

//指定位置之后插入数据
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);//接收新节点ListNode* newnode = LTBuyNode(x);//让新节点指向pos节点newnode->prev = pos;//让新节点指向pos前一个节点newnode->next = pos->next;//让pos节点前一个节点的后面指向新节点pos->next->prev = newnode;//让pos节点指向新节点pos->next = newnode;
}

十、指定位置头插

指定位置头插其实和指定位置尾插很类型,将条件改成相反即可
形式:

//指定位置之前插入数据
void ListInsertend(ListNode* pos, LTDataType x);

函数实现:

//指定位置之前插入数据
void ListInsertend(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = LTBuyNode(x);//让新节点前面指向pos节点newnode->next = pos;//让新节点后面指向pos后一个节点newnode->prev = pos->prev;//让pos后一个节点的前面指向新节点pos->prev->next = newnode;//让pos后一个指向新节点pos->prev = newnode;
}

十一、指定位置删除

形式:

//删除指定节点
void ListPop(ListNode* pos);

函数实现:

//删除指定节点
void ListPop(ListNode* pos)
{assert(pos);//pos->perv pos pos->nextpos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
http://www.dtcms.com/a/568641.html

相关文章:

  • 《Linux系统编程之开发工具》【版本控制器 + 调试器】
  • C++ :C宏函数的升级:内联函数inline
  • 青海网站建设费用织梦后台怎么建设网站
  • [特殊字符] Gudu SQL Omni 在数据治理体系中的落地实践指南
  • arm寄存器虚拟化分析
  • Linux网络传输层TCP协议
  • 做企业网站备案收费吗怎么修改网站标题
  • 机器视觉---Intel RealSense SDK 2.0 开发流程
  • 【AI基础篇】Transformer架构深度解析与前沿应用
  • QuickNacos
  • 用Python来学微积分30-微分方程初步
  • Opencv(七) : 图像颜色替换
  • Skywalking运维之路(Skywalking服务搭建)
  • 网站开发及建设赔偿条款中国最牛的十大企业
  • 广州全运会即将开幕,获得文远知行自动驾驶技术支持
  • 在智能制造语境下理解ISA-95、IIoT和UNS
  • 网站建设 服务器 预算报价清单企业展厅设计公司虎
  • 算法学习入门---前缀和(C++)
  • 一键生成系统架构图
  • 2025国产MOM系统全景透视:谁在领跑智能制造新赛道?
  • 系统架构设计师备考第64天——网络构建关键技术
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第三十五讲)
  • 网站备案个人可以做吗thinkphp cms开源系统
  • 一般电脑网站建设及运营多少钱中国最新军事新闻报道
  • Elasticsearch+Logstash+Filebeat+Kibana部署[7.17.3版本]
  • Elasticsearch单机部署全指南
  • 前端实战开发(三):Vue+Pinia中三大核心问题解决方案!!!
  • 从零开始:开发一个仓颉三方库的完整实战
  • 本机 MongoDB 注册系统服务、启用security认证
  • Nginx代理配置的“双斜杠陷阱“:从IP到域名的完整排查与解决指南