[C语言][数据结构][链表] 双链表的从零实现!

devtools/2024/11/15 0:59:45/

目录

零.必备知识

0.1 一级指针 && 二级指针

0.2 双链表节点的成员列表

        a. 数据

        b. 后驱指针

        c. 前驱指针

0.3 动态内存空间的开辟

一. 双链表的实现与销毁

        1.1 节点的定义

        1.2 双向链表的初始化 && 创建新节点

        1.3 尾插 

        1.4 头插 

        1.5 尾删 

        1.6 头删

        1.7 在指定位置之后插入数据 

        1.8 删除指定位置的数据 

        1.9 销毁双链表

 二.双链表源码

List.h

List.c


零.必备知识

0.1 一级指针 && 二级指针

0.2 双链表节点的成员列表

        a. 数据

        b. 后驱指针

        c. 前驱指针

0.3 动态内存空间的开辟


一. 双链表的实现与销毁

此次实现的链表带头双向循环链表.如图:

        1.1 节点的定义

后驱节点指向写一个节点.

前驱节点指向前一个节点.

        1.2 双向链表的初始化 && 创建新节点

初始化是指:创建了一个哨兵节点.

这个哨兵节点的作用是:避免双向链表死循环.

 

        1.3 尾插 

贯穿本文的核心要点是:先修改新节点中前驱,后驱指针的指向.再修改其余节点前驱,后驱指针的指向.

        1.4 头插 

 

        1.5 尾删 

尾删和接下来的头删,都是可以创建一个临时指针来指向要删除的节点.

这样看以来更直观,更重要的是方便进行空间的释放.

 

        1.6 头删

 

 

        1.7 在指定位置之后插入数据 

在指定位置之后插入数据:可以先实现一个查找函数,来自己指定pos.

 

        1.8 删除指定位置的数据 

 

        1.9 销毁双链表

 注意:此处传入的是一级指针,并没有真正的修改phead(哨兵位/头节点)中的值,如果要真正的销毁双链表,需要传入二级指针.

那么为什么我传的是一级指针呢?  答案:保持传入参数的一致性.

 

 二.双链表源码

List.h

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>// 单个节点的定义
typedef int LTDateType;
typedef struct List
{LTDateType date;struct List* next; // 后驱节点struct List* prev; // 前驱节点
}List;
// 节点的创建
List* LTBuyNode(LTDateType x);
// 双向链表的初始化
List* LTInit();
// 双向链表的展示
void LTPrint(List* phead);
// 尾插-头插
void LTPushBack(List* phead, LTDateType x);
void LTPushFront(List* phead, LTDateType x);
// 尾删-头删
void LTPopBack(List* phead);
void LTPopFront(List* phead);
// 查找指定数据
List* LTFind(List* phead, LTDateType x);
// 在指定位置之后插入数据
void LTInsertAfter(List* pos, LTDateType x);
// 删除指定位置的数据
void LTErase(List* phead, List* pos);
// 销毁双链表
void LTDestroy(List* phead);

List.c

#define  _CRT_SECURE_NO_WARNINGS
#include "List.h"
// 节点的创建
List* LTBuyNode(LTDateType x)
{List* newNode = (List*)malloc(sizeof(List));if (newNode == NULL) { // 创建失败perror("malloc fail!");exit(1);}newNode->date = x;newNode->next = NULL;newNode->prev = NULL;return newNode;
}
// 双向链表的初始化
List* LTInit()
{List* newNode = LTBuyNode(-1);newNode->next = newNode;newNode->prev = newNode;return newNode;
}
// 双向链表的展示
void LTPrint(List* phead)
{assert(phead);List* pcur = phead->next;while (pcur != phead) {printf("%d->", pcur->date);pcur = pcur->next;}printf("\n");
}
// 尾插
void LTPushBack(List* phead, LTDateType x)
{assert(phead);// 创建新节点List* newNode = LTBuyNode(x);// 先更改新节点的前驱后驱指针newNode->next = phead;newNode->prev = phead->prev;// 更改其余节点的前驱后驱指针phead->prev->next = newNode;phead->prev = newNode;
}
// 头插
void LTPushFront(List* phead, LTDateType x)
{assert(phead);List* newNode = LTBuyNode(x);// 更改newNode的前驱后驱指针newNode->next = phead->next;newNode->prev = phead;// 更改其余节点的指针指向phead->next->prev = newNode;phead->next = newNode;
}
// 尾删
void LTPopBack(List* phead)
{assert(phead);List* pcur = phead->prev;// 更改要删除的节点的前一个节点的指针指向pcur->prev->next = phead;// 更改哨兵位的指针指向phead->prev = pcur->prev;// 释放尾节点free(pcur);pcur = NULL;
}
// 头删
void LTPopFront(List* phead)
{assert(phead);List* pcur = phead->next;phead->next = pcur->next;pcur->next->prev = phead;// 销毁pcur节点free(pcur);pcur = NULL;
}
// 查找指定数据
List* LTFind(List* phead, LTDateType x)
{assert(phead);List* pcur = phead->next;while (pcur != phead) {if (pcur->date == x) {printf("找到了!\n");return pcur;}pcur = pcur->next;}printf("没有找到!\n");return NULL;
}
// 在指定位置之后插入数据
void LTInsertAfter(List* pos, LTDateType x)
{assert(pos);List* newNode = LTBuyNode(x);// 修改新节点的指向newNode->next = pos->next;newNode->prev = pos;// 修改其余节点的指向pos->next->prev = newNode;pos->next = newNode;
}
// 删除指定位置的数据
void LTErase(List* phead, List* pos)
{assert(phead && pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;// 销毁pos节点free(pos);pos = NULL;
}
// 销毁双链表
void LTDestroy(List* phead)
{assert(phead);List* pcur = phead->next;while (pcur != phead) {List* prev = pcur;pcur = pcur->next;// 销毁 pcur的前一个节点free(prev);prev = NULL;}// 销毁哨兵位free(phead);phead = NULL; // 但是没有真正的改变哨兵位, 因为传的是一级指针// 如果要真正的改变 phead的值,则需要传递二级指针
}


http://www.ppmy.cn/devtools/6451.html

相关文章

关于机器学习/深度学习的一些事-答知乎问(六)

如何使用频率域变换对序列数据进行增强&#xff1f; 时频变换是常见的信号分析思路&#xff0c;同样可用于数据增强。在频率域添加噪声是方法之一。比如可以对传感器信号应用短时傅里叶变换STFT得到具有时序关系的谱特征&#xff0c;再在谱特征上应用两种数据增强方法。一是对…

【树莓派学习】hello,world!

系统安装及环境配置详见【树莓派学习】系统烧录及VNC连接、文件传输-CSDN博客 树莓派内置python3&#xff0c;可以直接利用python输出。

关于外网java后端服务访问内网minio中间件,因连接minio超时,启动失败问题

注&#xff1a;服务器情况&#xff1a;2台服务器&#xff0c;内网服务器包含&#xff08;activemq、minio、nginx、redis、mysql、后端java服务&#xff09;。外网服务器只有后端java服务&#xff0c;访问内网的中间件&#xff08;内网服务器开放了部分指定端口&#xff09; 问…

vscode远程ubuntu16安装失败

vscode1.85版本之后不支持ubuntu16了&#xff0c;需要的同学&#xff0c;可下载1.85便携版使用。https://github.com/microsoft/vscode/issues/203967#issuecomment-1923440629 下载地址&#xff1a;https://vscode.download.prss.microsoft.com/dbazure/download/stable/8b377…

XiaodiSec day027 Learn Note 小迪渗透学习笔记

XiaodiSec day027 Learn Note 小迪渗透学习笔记 记录得比较凌乱&#xff0c;不尽详细 27day 还是 sql 知识点 数据类型注入&#xff1a; 数字型&#xff0c;字符型&#xff0c;搜索型&#xff0c;加密型 开始 数字型 数字型是 0-9 字符型 字符型是 a-z 等 在接收 sql …

Adobe Premiere Pro将加入AI生成式功能,以提高视频编辑的效率;OpenAI宣布在东京设立亚洲首个办事处

&#x1f989; AI新闻 &#x1f680; Adobe Premiere Pro将加入AI生成式功能&#xff0c;以提高视频编辑的效率 摘要&#xff1a;Adobe宣布&#xff0c;将为Premiere Pro引入由生成式AI驱动的新功能&#xff0c;以提高视频编辑的效率。这些功能包括“生成扩展”&#xff0c;能…

便携式网络音视频解码器JR-SMD201-P

详细介绍&#xff1a; JR-SMD201-P便携式网络解码器采用1/2U设计&#xff0c;支持AVS/H.265/H.264/MPEG2解码&#xff0c;支持IP输入&#xff0c;支持1080P/1080I/720P/576I/480I多种分辨率&#xff0c;支持DRA/AC3/EAC3/AAC/MPEG等音频。 产品特点 支持输入方式IP 接口丰富&a…

23种设计模式之创建型模式篇

一、创建型模式 这类模式主要关注对象的创建过程。它们试图在创建对象的同时&#xff0c;将对象的创建和使用分离&#xff0c;以达到更高的灵活性和可扩展性. 包括: 工厂方法模式&#xff08;Factory Method&#xff09;抽象工厂模式&#xff08;Abstract Factory&#xff0…