【数据结构与算法】之双向链表及其实现!

                                                                                个人主页:秋风起,再归来~

                                                                                            数据结构与算法                             

                                                                       个人格言:悟已往之不谏,知来者犹可追

                                                                                        克心守己,律己则安!

目录

1、双向链表的结构及概念

2、双向链表的实现

2.1 要实现的接口(List.h)

2.2 链表的初始化

2.3 链表的销毁

2.4 链表的打印

2.5 链表的尾插

2.6 链表的尾删

2.7 链表的头插

2.8 链表的头删

2.8 链表的查找

2.9 pos位置插入数据

2.10 pos位置删除数据

3、完整代码

List.h

List.c

Test.c(本人在实现双向链表时的测试代码) 

4、 完结散花


1、双向链表的结构及概念

我们这里要实现的数据结构是带头双向循环的链表(简称双向链表)

下面就是该链表的物理模型啦~

2、双向链表的实现

2.1 要实现的接口(List.h)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev;//前驱指针LTDataType data;struct ListNode* next;//后驱指针
}LTNode;//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();//链表销毁
void LTDestroy(LTNode* phead);//链表的打印
void LTPrint(LTNode* phead);//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);//链表的尾删
void LTPopBack(LTNode* phead);//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);//链表的头删
void LTPopFront(LTNode* phead);//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);//删除pos位置节点
void LTErase(LTNode* pos);

2.2 链表的初始化

注意:在初始化的时候一定要让头结点的prev指针和next指针都指向自己!

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{//初始化时创建一个带哨兵卫的头结点LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail!\n");return NULL;}phead->next = phead->prev = phead;phead->data = -1;return phead;
}

2.3 链表的销毁

注意:我们一定是从链表的头结点(头结点中并没有有效数据的存储)的下一个位置开始销毁链表的!

//链表销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur=phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur!=phead){next = pcur->next;free(pcur);pcur = next;}
}

并且我们在调用链表的销毁函数后依然要手动释放动态内存开辟的phead头结点 !

为尽量确保接口传递参数的一致性我们并没有传递头结点的地址,所以我们并不能在链表的销毁函数中free我们的头结点!

LTDestroy(plist);
//动态开辟的头结点需要手动释放
free(plist);
plist = NULL;

2.4 链表的打印

遍历链表打印头结点,循环结束的条件是pcur=phead,继续的条件是pcur!=phead

//链表的打印
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){next = pcur->next;printf("%d->", pcur->data);pcur = next;}printf("\n");
}

2.5 链表的尾插

新节点的创建(单独封装成为一个函数)

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空NewNode->prev = NULL;return NewNode;
}

链表的尾插 (在为尾插接口中直接调用创建节点的函数)

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点LTNode* tail = phead->prev;newNode->prev = tail;newNode->next = phead;tail->next = newNode;phead->prev = newNode;
}

2.6 链表的尾删

注意各个节点的指向!

//链表的尾删
void LTPopBack(LTNode* phead)
{//尾删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* tail = phead->prev;phead->prev = tail->prev;tail->prev->next=phead;free(tail);tail = NULL;
}

2.7 链表的头插

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = phead->next;newNode->prev = phead;phead->next->prev = newNode;phead->next = newNode;
}

2.8 链表的头删

//链表的头删
void LTPopFront(LTNode* phead)
{//头删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* start = phead->next;phead->next = start->next;start->next->prev = phead;free(start);start= NULL;
}

2.8 链表的查找

返回值是该指向该数据节点的结构体指针,如没有找到,直接返回空!

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){if (pcur->data == x){return pcur;}next = pcur->next;pcur = next;}return NULL;
}

2.9 pos位置插入数据

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}

2.10 pos位置删除数据

//删除pos位置节点
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

3、完整代码

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev;//前驱指针LTDataType data;struct ListNode* next;//后驱指针
}LTNode;//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();//链表销毁
void LTDestroy(LTNode* phead);//链表的打印
void LTPrint(LTNode* phead);//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);//链表的尾删
void LTPopBack(LTNode* phead);//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);//链表的头删
void LTPopFront(LTNode* phead);//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);//删除pos位置节点
void LTErase(LTNode* pos);

List.c

#include"List.h"//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{//初始化时创建一个带哨兵卫的头结点LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail!\n");return NULL;}phead->next = phead->prev = phead;phead->data = -1;return phead;
}//链表销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur=phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur!=phead){next = pcur->next;free(pcur);pcur = next;}
}//链表的打印
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){next = pcur->next;printf("%d->", pcur->data);pcur = next;}printf("\n");
}//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间if (NewNode == NULL)//判断空间是否开辟成功{perror("malloc fail");return NULL;}NewNode->data = x;//赋值NewNode->next = NULL;//置空NewNode->prev = NULL;return NewNode;
}//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点LTNode* tail = phead->prev;newNode->prev = tail;newNode->next = phead;tail->next = newNode;phead->prev = newNode;
}//链表的尾删
void LTPopBack(LTNode* phead)
{//尾删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* tail = phead->prev;phead->prev = tail->prev;tail->prev->next=phead;free(tail);tail = NULL;
}//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = phead->next;newNode->prev = phead;phead->next->prev = newNode;phead->next = newNode;
}//链表的头删
void LTPopFront(LTNode* phead)
{//头删的前提是双向链表不为空assert(phead && phead->next != phead);LTNode* start = phead->next;phead->next = start->next;start->next->prev = phead;free(start);start= NULL;
}//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;LTNode* next = NULL;//结束条件是当pcur不等于篇pheadwhile (pcur != phead){if (pcur->data == x){return pcur;}next = pcur->next;pcur = next;}return NULL;
}//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = ListCreatNode(x);//先创建一个新节点newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}//删除pos位置节点
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

Test.c(本人在实现双向链表时的测试代码) 

#define _CRT_SECURE_NO_WARNINGS#include"LIst.h"void TestList1()
{LTNode* plist;plist = LTInit();//初始化链表LTPushBack(plist,1);LTPushBack(plist,2);LTPushBack(plist,3);LTPushFront(plist, 4);LTPushFront(plist, 4);/*LTPopFront(plist);LTPopFront(plist);*/LTNode* pos=LTFind(plist, 2);printf("删除pos位置之前\n");LTPrint(plist);LTErase(pos);printf("删除pos位置之后\n");LTPrint(plist);//LTInsert(pos, 5);//LTPopBack(plist);//LTPopBack(plist);//LTPopBack(plist);LTDestroy(plist);//动态开辟的头结点需要手动释放free(plist);plist = NULL;
}int main()
{TestList1();return 0;
}

4、 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

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

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

相关文章

深度学习 Lecture 8 决策树

一、决策树模型&#xff08;Decision Tree Model) 椭圆形代表决策节点&#xff08;decison nodes)&#xff0c;矩形节点代表叶节点&#xff08;leaf nodes)&#xff0c;方向上的值代表属性的值&#xff0c; 构建决策树的学习过程&#xff1a; 第一步&#xff1a;决定在根节点…

Towards Street-Level Client-Independent IP Geolocation(2011年)(第一部分)

被引次数:306 Wang Y, Burgener D, Flores M, et al. Towards {Street-Level}{Client-Independent}{IP} Geolocation[C]//8th USENIX Symposium on Networked Systems Design and Implementation (NSDI 11). 2011. Abstract 一个高度精确的客户端独立的地理定位服务将是互联…

箭头函数有哪些不适用场景

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

元类的执行

class MetaB(type):def __new__(cls, name, bases, attrs):print(f"使用元类 {cls.__name__} 创建{name}类 ")return super().__new__(cls, name, bases, attrs)class A(metaclassMetaB):passclass C(A):pass元类MetaB的__new__方法应该只会在创建类A时被调用一次, 因…

单例模式五种写法

单例模式五种写法 单例模式有五种写法&#xff1a;饿汉、懒汉、双重检验锁、静态内部类、枚举. 单例模式属于设计模式中的创建型模式 一、单例模式应用场景 windows的task manager(任务管理器)就是很典型的单例模式; windows的recycle bin(回收站)也是典型的单例应用&#…

Composer是什么?

Composer是PHP的一个依赖管理工具&#xff0c;它允许开发者声明项目所依赖的代码库&#xff0c;并在项目中自动安装这些依赖。它使用composer.json文件来定义项目的依赖关系&#xff0c;并使用composer.lock文件来锁定依赖的版本&#xff0c;以确保项目的稳定性和可重复性。 Co…

物联网的核心价值是什么?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff0c;这个词汇在当今的科技领域已经变得耳熟能详。但当我们深入探索物联网的核心价值时&#xff0c;我们会发现它远不止是一个简单的技术概念&#xff0c;而是一种能够彻底改变我们生活方式和工作方式的革命性力量。 物联网…

力扣周赛392复盘

3105. 最长的严格递增或递减子数组 题目 给你一个整数数组 nums 。 返回数组 nums 中 严格递增 或 严格递减 的最长非空子数组的长度。 思考&#xff1a; 返回什么&#xff1a;返回最长非空子数组的长度。return max(decs_len,incs_len); 但实际上我们只需要用一个变量ans就…

[leetcode] max-area-of-island

. - 力扣&#xff08;LeetCode&#xff09; 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&…

Java | Leetcode Java题解之第22题括号生成

题目&#xff1a; 题解&#xff1a; class Solution {static List<String> res new ArrayList<String>(); //记录答案 public List<String> generateParenthesis(int n) {res.clear();dfs(n, 0, 0, "");return res;}public void dfs(int n ,int…

牛客网刷题 | BC51 及格分数

描述 KiKi想知道他的考试分数是否通过&#xff0c;请帮他判断。从键盘任意输入一个整数表示的分数&#xff0c;编程判断该分数是否在及格范围内&#xff0c;如果及格&#xff0c;即&#xff1a;分数大于等于60分&#xff0c;是输出“Pass”&#xff0c;否则&#xff0c;输出“…

利用vite创建vue项目

创建vue项目步骤 打开HBuilder X工具&#xff0c;创建空白项目 进入终端(鼠标点击文件进行选择&#xff0c;然后终端) 利用vite脚手架创建项目 &#xff08;前提要将HBuilder X工具属性设为管理员运行状态&#xff08;属性》兼容》管理员身份运行此程序&#xff09; npm …

Ubuntu22.04配置ROS2+PX4仿真环境

Ubuntu22.04配置ROS2PX4仿真环境 主要参考源&#xff1a; https://blog.csdn.net/weixin_44174421/article/details/135827130 https://blog.csdn.net/Zecet/article/details/130474620 一、准备工作 确保网络能够连接到github&#xff0c;出错主要源自于此&#xff1b;确保…

【Qt 学习笔记】Qt常用控件 | 按钮类控件Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件Check Box的使用及说明 文章编号&#xff1a;…

C# 两种方法截取活动窗口屏幕,实现窗体截图

方法1&#xff0c;截屏内容仅包括活动窗口界面&#xff0c;而方法2是从屏幕范围取图&#xff0c;截屏内容会包括屏幕上所有内容。例如有一些程序在桌面顶层显示半透明的悬浮窗&#xff0c;用方法2截屏就会包括这些内容&#xff0c;并不是单纯的活动窗口内容。 方法1&#xff0c…

解决 MSYS2 Qt 6.7 默认 stylesheet 在 windows 11 下的显示故障

项目场景&#xff1a; MSYS2 升级到 Qt6.7.0&#xff0c;发现显示故障&#xff0c;所有Qt6程序以及 QtCreator的SpinBox都显示不全&#xff0c;Combox的底色不对。 问题描述 2024年4月1日&#xff0c;pacman升级MSYS2后&#xff0c;Qt6遇到风格错误。如果使用官方的 Qt onlin…

【PostgreSQL里insert on conflict do操作时的冲突报错分析】

最近在巡检PostgreSQL的数据库的时候&#xff0c;发现部分数据库里存在大量的如下报错 ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained val…

Winform重难点笔记

FrmMain.cs 中的 partial&#xff08;部分的&#xff09; 和 FrmMain.Designer.cs 中的 partial 一样&#xff0c;不是一个类的修饰符&#xff0c;是限定这个类本身的组成部分&#xff0c;叫做部分类。当程序在编译和运行时&#xff0c;会把 FrmMain.cs 中的 FrmMain 类 和 Frm…

前端console用法分享

console对于前端人员来讲肯定都不陌生&#xff0c;相信大部分开发者都会使用console来进行调试&#xff0c;但它能做的绝不仅限于调试。 最常见的控制台方法 作为开发者&#xff0c;最常用的 console 方法如下&#xff1a; 控制台打印结果&#xff1a; 今天我分享的是一些 co…

RabbitMQ Stream插件使用详解

2.4版为RabbitMQ流插件引入了对RabbitMQStream插件Java客户端的初始支持。 RabbitStreamTemplateStreamListener容器 将spring rabbit流依赖项添加到项目中&#xff1a; <dependency><groupId>org.springframework.amqp</groupId><artifactId>sprin…