C语言/数据结构——(用双链表实现数据的增删查改)

一.前言

嗨嗨嗨,大家好久不见!前面我们已经通过数组实现数据的增删查改、单链表实现数据的增删查改,现在让我们尝试一下使用双链表实现数据的增删查改吧!

二.正文

如同往常一样,对于稍微大点的项目来说,我们使用多文件操作更好一些。

List.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int DataType;
struct ListNode
{DataType data;struct ListNode* prev;struct ListNode* next;
};
typedef struct ListNode ListNode;
void ListInit(ListNode** pphead);//双向链表初始化
void ListDestroy(ListNode* phead);//双向链表销毁
void ListPrint(ListNode* phead);//双向链表打印
void ListPushBack(ListNode* phead, DataType x);//双向链表尾部插入节点
void ListPushFront(ListNode* phead, DataType x);//双向链表第一个有效节点前插入节点(即哨兵位的后一个节点为第一个有效节点)
void ListPopBack(ListNode* phead);//双向链表删除尾部节点
void ListPopFront(ListNode* phead);//双向链表删除第一个有效节点
void ListInsert(ListNode* pos, DataType x);//双向链表指定位置之后插入节点
void ListErase(ListNode* pos);//双向链表指定位置之后删除节点
ListNode* ListFind(ListNode* phead, DataType x);//查找目标节点

我们首先定义了这个项目的头文件,并把其他函数实现过程中可能使用的头文件包含在List.h中,这样就可以方便代码的维护,并且减少冗余的代码。

这是我们的双向链表结构体的定义:

struct ListNode
{DataType data;struct ListNode* prev;struct ListNode* next;
};

和单链表区分的话,那就是单链表定义的结构体中没有指向前一个节点的prev。

下面是我们在实现整个项目中,各种功能的罗列,就类似于书的目录一样。

void ListInit(ListNode** pphead);//双向链表初始化
void ListDestroy(ListNode* phead);//双向链表销毁
void ListPrint(ListNode* phead);//双向链表打印
void ListPushBack(ListNode* phead, DataType x);//双向链表尾部插入节点
void ListPushFront(ListNode* phead, DataType x);//双向链表第一个有效节点前插入节点(即哨兵位的后一个节点为第一个有效节点)
void ListPopBack(ListNode* phead);//双向链表删除尾部节点
void ListPopFront(ListNode* phead);//双向链表删除第一个有效节点
void ListInsert(ListNode* pos, DataType x);//双向链表指定位置之后插入节点
void ListErase(ListNode* pos);//双向链表指定位置之后删除节点
ListNode* ListFind(ListNode* phead, DataType x);//查找目标节点

List.c

#include"List.h"
ListNode* ListBuyNode(DataType x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("malloc fail!");return NULL;}node->next = node;//值得注意的是在创造新节点的时候,双向链表和单链表不同的是,单链表的next要指向NULLnode->prev = node;//而双向链表需要前后指针,即prev和next都要指向自己,构成循环。node->data = x;return node;
}
void ListInit(ListNode** pphead)
{*pphead = ListBuyNode(0);
}
void ListPushBack(ListNode* phead,DataType x)
{assert(phead);//双向链表的哨兵位phead不可能为NULL,如果phead都是NULL了,那么这个链表就不是双向链表。ListNode* newnode = ListBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev-> next = newnode;phead->prev = newnode;
}
void ListPrint(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
void ListPushFront(ListNode* phead,DataType x)
{assert(phead);ListNode* newnode = ListBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}
void ListPopBack(ListNode* phead)
{assert(phead && phead->next != phead);ListNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
void ListPopFront(ListNode* phead)
{assert(phead&&phead->next!=phead);ListNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}
ListNode* ListFind(ListNode* phead,DataType x)
{ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
void ListInsert(ListNode* pos,DataType x)
{ListNode* newnode = ListBuyNode(x);newnode->prev = pos;newnode->next = pos->next;pos->prev = newnode;pos->next = newnode;
}
void ListErase(ListNode* pos)
{pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
void ListDestroy(ListNode* phead)//值得注意的是因为这里我们传的的是一级指针,
//所以在这里phead的改变,不会影响实参plist的改变,因此在主函数中我们需要主动让plist=NULL。
//在这里我们也可以传二级指针ListNode** pphead,这样*pphead的改变就可以影响实参plist的改变了。
{ListNode* pcur = phead;ListNode* next = pcur->next;while (pcur != phead){free(pcur);pcur = next;next = next->next;}free(phead);
}

上面的代码中,个别函数有些地方的关键点我有注释,有兴趣的小伙伴可以看一下。

test.c

#include"List.h"
//void test1()//测试双向链表初始化和创造节点
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//}
//void test2()//测试双向链表尾插节点以及双向链表的打印
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//	ListPushBack(plist, 1);
//	ListPushBack(plist, 2);
//	ListPushBack(plist, 3);
//	ListPushBack(plist, 4);
//	ListPrint(plist);
//}
//void test3()//测试打印双向链表
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//	ListPushFront(plist, 1);
//	ListPushFront(plist, 2);
//	ListPushFront(plist, 3);
//	ListPushFront(plist, 4);
//	ListPrint(plist);
//}
//void test4()//测试尾删
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//	ListPushBack(plist, 1);
//	ListPushBack(plist, 2);
//	ListPushBack(plist, 3);
//	ListPushBack(plist, 4);
//	ListPopBack(plist);//删一次
//	ListPopBack(plist);//删两次
//	ListPopBack(plist);//三次
//	ListPopBack(plist);//四次,下一次再删除会报错
//	ListPrint(plist);
//}
//void test5()//测试头删
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//	ListPushBack(plist, 1);
//	ListPushBack(plist, 2);
//	ListPushBack(plist, 3);
//	ListPushBack(plist, 4);
//	ListPrint(plist);
//	ListPopFront(plist);//删一次
//	ListPrint(plist);
//	ListPopFront(plist);//2
//	ListPrint(plist);
//	ListPopFront(plist);//3
//	ListPrint(plist);
//	ListPopFront(plist);//4,下一次再删除会报错
//	ListPrint(plist);
//}
//void test6()//测试查找目标数据节点、指定节点之后插入数据、删除指定节点
//{
//	ListNode* plist = NULL;
//	ListInit(&plist);
//	ListPushBack(plist, 1);
//	ListPushBack(plist, 2);
//	ListPushBack(plist, 3);
//	ListPushBack(plist, 4);
//	ListNode* find = ListFind(plist, 1);
//	if (find == NULL)
//		printf("没找到\n");
//	else
//		printf("找到了,地址是%p\n", find);
//	ListErase(find);
//	ListPrint(plist);
//}
void test7()//链表的销毁
{ListNode* plist = NULL;ListInit(&plist);ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);ListDestroy(plist);ListPrint(plist);plist = NULL;
}
int main()
{//test1();//测试双向链表初始化和创造节点//test2();//测试双向链表尾插节点以及双向链表的打印//test3();//测试打印双向链表//test4();//测试尾删//test5();//测试头删//test6();//测试查找目标数据节点、指定节点之后插入数据、删除指定节点test7();//链表的销毁return 0;
}

上面的代码,是我在测试函数功能是否正常所写的代码。小伙伴们看看就行。

三.结言

关于如何通过双向链表实现数据的增删查改就讲到这里了,帅哥美女们,我们下次再见,拜拜。

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

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

相关文章

推荐网站(2)今日热榜合集,看不同软件的热点事件

当我们想要看微博&#xff0c;今日头条&#xff0c;bilibili等等今日热点时&#xff0c;需要打开对应的app查看&#xff0c;但是有了这个网站我们可以看不同平台的热点消息&#xff0c;甚至京东&#xff0c;淘宝等购物软件&#xff0c;也能看到热销总榜。 链接直达&#xff1a;…

day-28 除自身以外数组的乘积

思路 利用两个空数组left&#xff08;left[i]表示nums下标从0到i的乘积&#xff09;和right&#xff08;right[i]表示nums下标从n-1到i的乘积&#xff09; 解题方法 返回数组nums[i]right[i1]*left[i-1],第一个元素和最后一个元素单独考虑 Code class Solution {public int[…

【计算机网络】计算机网络的定义和分类

一.定义 计算机网络并没有一个精确和统一的定义&#xff0c;在计算机网络发展的不同阶段&#xff0c;人们对计算机网络给出了不同的定义&#xff0c;这些定义反映了当时计算机网络技术的发展水平。 例如计算机网络早期的一个最简单定义&#xff1a;计算机网络是一些互连的、自…

c#Excel:2.写入Excel表 3.读取Excel表

--写入Excel表-- 该例首先从数据库aq中读取学生信息表staq(参考数据库章节)&#xff0c;然后将学生信息表中的数据写入Excel表格中 &#xff08;1&#xff09;在OfficeOperator类库项目的ExcelOperator类中定义索引器&#xff0c;用于获取Excel表格中的单元格&#xff0c;代码…

队列集使用

文章目录 前言一、功能描述二、功能实现 前言 在实际使用中我们可能要从多个队列中得到数据&#xff0c;就需要队列集。 一、功能描述 创建两个队列Queue1、Queue2。Task1往Queue1写数据、Task2往Queue2写数据。Task3使用QueueSet监测这两个队列。 二、功能实现 创建两个队列…

ArcGIS软件:地图投影的认识、投影定制

这一篇博客介绍的主要是如何在ArcGIS软件中查看投影数据&#xff0c;如何定制投影。 1.查看地图坐标系、投影数据 首先我们打开COUNTIES.shp数据&#xff08;美国行政区划图&#xff09;&#xff0c;并点击鼠标右键&#xff0c;再点击数据框属性就可以得到以下的界面。 我们从…

活动图与状态图:UML中流程图的精细化表达——专业解析系统动态性与状态变迁

流程图是一种通用的图形表示法&#xff0c;用以展示步骤、决策和循环等流程控制结构。它通常用于描述算法、程序执行流程或业务过程&#xff0c;关注于任务的顺序执行。流程图强调顺序、分支和循环&#xff0c;适用于详细说明具体的处理步骤&#xff0c;图形符号相对基础和通用…

Android NDK开发——Android Studio 3.5.2安装与配置踩坑

Android NDK开发——Android Studio 3.5.2安装与配置踩坑 一、Android Studio下载二、配置踩坑报错1&#xff1a;Failed to install the following Android SDK packages as some licences have not been accepted报错2&#xff1a;No toolchains found in the NDK toolchains …

C语言实战项目--贪吃蛇

贪吃蛇是久负盛名的游戏之一&#xff0c;它也和俄罗斯⽅块&#xff0c;扫雷等游戏位列经典游戏的行列。在编程语言的教学中&#xff0c;我们以贪吃蛇为例&#xff0c;从设计到代码实现来提升大家的编程能⼒和逻辑能⼒。 在本篇讲解中&#xff0c;我们会看到很多陌生的知识&…

Unity 性能优化之数据面板(Statistics)(一)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、unity 统计数据面板&#xff08;Statistics&#xff09;1.Audio属性2.Graphics属性 二、什么是Draw Call&#xff1f;三、Unity3D stats也可以通过代…

OpenCV的周期性噪声去除滤波器(70)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV如何通过梯度结构张量进行各向异性图像分割(69) 下一篇 :OpenCV如何为我们的应用程序添加跟踪栏(71) 目录 目标 理论 如何消除傅里叶域中的周期性噪声&#xff1f; 源代码 解释 结果 目…

设计模式之数据访问对象模式

在Java编程的浩瀚星海中&#xff0c;有一个模式低调却强大&#xff0c;它像是一位默默无闻的超级英雄&#xff0c;支撑起无数应用的数据脊梁——那就是数据访问对象&#xff08;DAO, Data Access Object&#xff09;模式&#xff01;想象一下&#xff0c;如果你能像操纵魔法一样…

Unity技术学习:RenderMesh、RenderMeshInstanced

叠甲&#xff1a;本人比较菜&#xff0c;如果哪里不对或者有认知不到的地方&#xff0c;欢迎锐评&#xff08;不玻璃心&#xff09;&#xff01; 导师留了个任务&#xff0c;渲染大量的、移动的物体。 当时找了几个解决方案&#xff1a; 静态批处理&#xff1a; 这东西只对静…

golang for经典练习 金字塔打印 示例 支持控制台输入要打印的层数

go语言中最经典的for练习程序 金字塔打印 &#xff0c;这也是其他语言中学习循环和条件算法最为经典的联系题。 其核心算法是如何控制内层循环变量j 每行打印的*号数量 j<i*2-1 和空格数量 j1 || j i*2-1 golang中实现实心金字塔 Solid Pyramid和空心金字塔 Hollow Pyram…

CSS浮动(如果想知道CSS有关浮动的知识点,那么只看这一篇就足够了!)

前言&#xff1a;在学习CSS排版的时候&#xff0c;浮动是我们必须要知道的知识点&#xff0c;浮动在设计之初是为了实现文字环绕效果的&#xff0c;但是后来被人们发现浮动在CSS排版中有着很好的实用价值&#xff0c;所以浮动便成为了CSS排版的利器之一。 ✨✨✨这里是秋刀鱼不…

pandas学习笔记12

缺失数据处理 其实在很多时候&#xff0c;人们往往不愿意过多透露自己的信息。假如您正在对用户的产品体验做调查&#xff0c;在这个过程中您会发现&#xff0c;一些用户很乐意分享自己使用产品的体验&#xff0c;但他是不愿意透露自己的姓名和联系方式&#xff1b; 还有一些用…

《尿不湿级》STM32 F103C8T6最小系统板搭建(五)BOOT

一、BOOT是什么&#xff1f; 大多数初学者第一次接触BOOT总是对这个词感到不解&#xff0c;从哪冒出一个奇奇怪怪的东西还要接跳线帽&#xff0c;为什么要配置它才能进行串口程序的下载&#xff1f;为什么不正确配置会导致单片机无法正常启动…… boot&#xff0c;及物动词&…

AI-数学-高中56-成对数据统计-线性回归方程

原作者视频&#xff1a;【成对数据统计】【一数辞典】1线性回归方程_哔哩哔哩_bilibili 注意&#xff1a;高中只学线性回归。 最小二乘法&#xff08;残差和平方最小的直线、方差最小>拟合程度最好&#xff09;&#xff1a;

2.spring security 简单入门

创建springboot 项目&#xff0c;引入spring security坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring security坐标--><dependency&g…

17_Scala面向对象高阶功能

文章目录 1.继承1.1 构造对象时,父类对象优于子类对象1.2父类主构造有参数,子类必须要显示地调用父类主构造器并传值 2.封装3.抽象3.1抽象定义3.2子类继承抽象类3.3抽象属性 4.伴生对象4.1创建类和伴生对象4.2调用 1.继承 –和Java一样,权限protected , public.父类定义子类用…