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

【数据结构】双链表--从原理到(用C语言)实现全解析

文章目录

  • 1.双链表的概念
    • 1.1概念
    • 1.2单链表与双链表的区别
    • 1.3双链表为空的情况
  • 2.双链表的功能
  • 3.双链表功能的实现
    • 3.1双链表的结构
    • 3.2初始化
    • 3.3销毁
    • 3.4链表的打印
    • 3.5判断链表是否为空
    • 3.6尾插
    • 3.7头插
    • 3.8尾删
    • 3.9头删
    • 3.10查找指定数据
    • 3.11在指定位置之后(前)插入
    • 3.12删除指定位置的节点
  • 4.完整代码
    • List.h
    • List.c
    • main.c
    • 运行结果

1.双链表的概念

1.1概念

双链表,即带头双向循环链表(链表有头节点,方相是双向的,且是循环的):它是一种逻辑结构为线性,物理结构不一定为线性的存储结构(与单链表基本一致)

1.2单链表与双链表的区别

  • 双链表相比单链表,新增了一个指针变量prev,用来指向前一个节点
  • 单链表中尾节点的next指针为空,而双链表中尾节点的next指针指向头节点,头节点的prev指针指向尾节点
  • 单链表是单向不循环的,而双链表是双向循环的
  • 单链表和双链表的头节点本质上是不同的,单链表中的“头节点” 由于存储了val值,所以 不是真正的头节点,仅用来表示第一个节点;而 双链表中的头节点 不存储有效数据val,只存储prev指针和next指针,所以这里的头节点 才是真正意义上的头节点,我们也可以把它称作“哨兵位”(表示只用来放哨,占一个位置,而不存储任何有效数据的节点)

综上,单链表是不带头单向不循环链表,双链表是带头双向循环链表
请添加图片描述

1.3双链表为空的情况

val中不存储有效数据,头指针head的next和prev指针都指向自己
请添加图片描述

2.双链表的功能

功能与单链表相似,同样可以概括为增删查改四个功能,但由于双链表增加了prev指针,所以可以大幅减小时间复杂度,提高代码效率

3.双链表功能的实现

3.1双链表的结构

typedef int LTDataType;//存储的数据类型
typedef struct ListNode{LTDataType val;//存储的数据struct ListNode* next;//指向后一个节点struct ListNode* prev;//指向前一个节点
}LTNode;

3.2初始化

创建一个空链表,添加一个头节点,它的next和prev指针都指向自己,其中不存储有效数据

这里我们需要先实现一个专门用来添加节点的函数

//添加节点
LTNode* LTBuyNode(int x)
{LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));if(newNode == NULL){perror("Malloc Failed!\n");exit(1);}newNode->val = x;newNode->next = newNode->prev = newNode;return newNode;
}

初始化函数:

LTNode* LTInit(void)
{//添加头节点 传入无效数据-1LTNode* phead = LTBuyNode(-1);return phead;
}

3.3销毁

遍历并删除头节点后的每一个节点

void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead)//pcur==phead时即为只有一个头节点的情况{LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);//phead = NULL;//函数是传值调用(传指针本身 没有传它的地址) 因此这一步没有意义
}

注:使用此函数后,要手动把头指针赋值为NULL,避免野指针的出现

3.4链表的打印

遍历头节点之后的所有节点,一个一个打印,直到当前节点的next指向头节点

void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;if(pcur != phead) printf("%d", pcur->val);pcur = pcur->next;while(pcur != phead){printf(" -> %d", pcur->val);pcur = pcur->next;}printf("\n");
}

3.5判断链表是否为空

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

3.6尾插

双链表是循环的,通过head->prev找到尾节点,把新节点放到尾节点后即可

void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->prev = phead->prev;newNode->next = phead;phead->prev->next = newNode;phead->prev = newNode;
}

注意:要先更新新节点的prev和next指针,如果先更新其他节点,那么再赋值给新节点,会出现错误

3.7头插

把新节点添加到头节点和第一个节点之间即可

void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->next = phead->next;newNode->prev = phead;phead->next = newNode;newNode->next->prev = newNode;
}

3.8尾删

通过head->prev节点找到尾节点,删除即可

void LTPopBack(LTNode* phead)
{//判断链表是否为空assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}

3.9头删

删除头节点和第一个节点之间的节点即可

void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}

3.10查找指定数据

遍历整个链表,直到节点数据值与指定数据值相等,返回该节点地址

LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead){if(pcur->val == x) return pcur;pcur = pcur->next;}return NULL;
}

3.11在指定位置之后(前)插入

在pos之后插入:

void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = LTBuyNode(x);newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}

在pos之前插入:

void LTInsertBefore(LTNode* pos, LTDataType x)
{assert(pos);//即为在pos前一个节点之后插入LTInsertAfter(pos->prev, x);
}

3.12删除指定位置的节点

void LTDeletePos(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);//pos = NULL;
}

注意:pos是传值调用,在函数内不能改变它的值,所以使用该函数时,记得在函数外把pos置为NULL

4.完整代码

List.h

//  List.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//双链表的结构
typedef int LTDataType;
typedef struct ListNode{LTDataType val;struct ListNode* next;struct ListNode* prev;
}LTNode;//添加节点
LTNode* LTBuyNode(int x);
//初始化
LTNode* LTInit(void);
//销毁
void LTDestroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//判空
bool LTEmpty(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);//在指定节点之后插入
void LTInsertAfter(LTNode* pos, LTDataType x);
//在指定节点之前插入
void LTInsertBefore(LTNode* pos, LTDataType x);//删除指定节点
void LTDeletePos(LTNode* pos);

List.c

//  List.c
#include "List.h"LTNode* LTBuyNode(int x)
{LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));if(newNode == NULL){perror("Malloc Failed!\n");exit(1);}newNode->val = x;newNode->next = newNode->prev = newNode;return newNode;
}
LTNode* LTInit(void)
{//添加头节点 传入无效数据-1LTNode* phead = LTBuyNode(-1);return phead;
}
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead)//pcur==phead时即为只有一个头节点的情况{LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);//phead = NULL;//函数是传值调用(传指针本身 没有传它的地址) 因此这一步没有意义
}
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;if(pcur != phead) printf("%d", pcur->val);pcur = pcur->next;while(pcur != phead){printf(" -> %d", pcur->val);pcur = pcur->next;}printf("\n");
}
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->prev = phead->prev;newNode->next = phead;phead->prev->next = newNode;phead->prev = newNode;
}
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->next = phead->next;newNode->prev = phead;phead->next = newNode;newNode->next->prev = newNode;
}void LTPopBack(LTNode* phead)
{//判断链表是否为空assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead){if(pcur->val == x) return pcur;pcur = pcur->next;}return NULL;
}void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = LTBuyNode(x);newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}
void LTInsertBefore(LTNode* pos, LTDataType x)
{assert(pos);//即为在pos前一个节点之后插入LTInsertAfter(pos->prev, x);
}void LTDeletePos(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);//pos = NULL;
}

main.c

#include "List.h"void test(void)
{LTNode* plist = LTInit();LTPrint(plist);LTPushBack(plist, 1);LTPrint(plist);LTPushBack(plist, 2);LTPrint(plist);LTPushFront(plist, 3);LTPrint(plist);LTPushFront(plist, 4);LTPrint(plist);printf("%p\n", LTFind(plist, 1));printf("%p\n", LTFind(plist, 5));LTInsertAfter(LTFind(plist, 4), 400);LTPrint(plist);LTInsertBefore(LTFind(plist, 2), 200);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);LTNode* pos = LTFind(plist, 200);LTDeletePos(pos);pos = NULL;LTPrint(plist);LTDestroy(plist);plist = NULL;
}
int main(void)
{test();return 0;
}

运行结果

请添加图片描述

http://www.dtcms.com/a/282187.html

相关文章:

  • 【Linux 学习指南】网络编程基础:从 IP、端口到 Socket 与 TCP/UDP 协议详解
  • 第二十一 篇 PDF文档自动化:Python一键合并、分割、水印、提取与加密解密!你的PDF全能管家!
  • [AI-video] 字幕服务 | 视频素材服务 | 视频生成服务
  • MySQL索引及其底层原理(上)(10)
  • C++ 计数排序、归并排序、快速排序
  • SpringCloud之Config
  • Android动态获取当前应用占用的内存PSS,Java
  • 【25】MFC入门到精通——MFC静态文本框 中字符串 连续输出 不覆盖先前的文本 换行输出
  • RK3568项目(十)--linux驱动开发之基础外设与LVDS屏幕
  • spring-ai之工具调用(Tool Calling)
  • 【18】MFC入门到精通——MFC(VS2019)+ OpenCV 显示图片的3种方法
  • Linux Java环境配置
  • xss-labs 1-8关
  • 智芯Z20K11xM MCU开发之TDG触发Adc
  • 新疆兵团第六师新湖农场食记
  • 企业级AI项目未达预期:非结构化数据处理背后有何玄机?
  • es启动问题解决
  • Python 日志轮换处理器的参数详解
  • 元宇宙经济:虚实融合引发经济新变革
  • audiorecord 之 抢占优先级
  • 【世纪龙科技】汽车发动机拆装检修仿真教学软件-仿真精进技能
  • JAVA进阶 项目实战:汽车租聘系统
  • 黄仁勋:早知道雷军会有非凡成功,想买一辆小米汽车
  • 汽车免拆诊断案例 | 2015款进口起亚索兰托L车漏电
  • 自动化框架 Selenium 的使用
  • C++ 异常处理、typeid
  • 霍尔电流传感器在新能源汽车中的应用综述
  • 量子计算实用算法:2025年突破性进展与产业落地全景
  • 汽车功能安全-在系统层面验证TSR实例
  • 【React Native】布局和 Stack 、Slot