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要指向NULL
	node->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.mfbz.cn/a/610861.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024洗地机选购指南 | 怎么选洗地机不会被坑?

家里的地板总是需要打扫&#xff0c;但工作忙碌的我们往往没有足够的时间来打理。洗地机不仅能够帮助我们节省宝贵的时间&#xff0c;还能让我们的家变得一尘不染。今天&#xff0c;笔者将为大家讲讲挑选洗地机的技巧&#xff0c;告诉大家怎么挑选洗地机不会被坑&#xff0c;顺…

解锁楼宇自动化新维度西门子Insight+BACnet IP I/O控制器

数字城市的楼宇自动化已不再是一个遥不可及的概念&#xff0c;而是成为了现代建筑的标配。特别是在大型商业综合体、高端写字楼和公共设施中&#xff0c;高效的楼宇管理系统是确保环境舒适度与能源效率的关键。当提及楼宇自动化领域的佼佼者&#xff0c;西门子Insight楼宇自动化…

【洛谷】动态规划之最长公共子序列

前言&#xff1a; 本系列目的是记录日常所刷的题&#xff0c;有的是自己想出来的题&#xff0c;有的是看了大佬题解后想明白的题 题目 P1439 【模板】最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前提&#xff1a; 两个排列都是1到n的排列&#xff0c;说…

如何使用Vite快速构建vue项目

1、在自己定义的目录下打开cmd命令窗口&#xff1a;如文件夹目录上面输入cmd回车就可以打开 2、检查 node环境&#xff1a;通过node --version看版本号表示安装好了 3、 使用Vite 快速构建Vue项目 npm init vitelatest qiuqiu.admin 注意&#xff1a;如何你电脑没有装vite首…

《2024网络安全报告》中文版

Check Point发布了《2024 年网络安全报告》&#xff0c;Check Point Research 对网络攻击数据&#xff08;包括所有地区和全球的统计数据&#xff09;进行了全面分析&#xff0c;揭示了不断变化的网络威胁形势。 ● 去年&#xff0c;全球 1/10 的机构遭遇勒索软件攻击尝试&a…

QTday5

目录 网络聊天室服务器 网络聊天室客户端 学生管理系统 网络聊天室服务器 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//实例化一个tcp服务器sernew QTcpSe…

【SpringBoot记录】自动配置原理(2):自动配置的案例说明

案例说明 通过前面案例可以很明显感受到&#xff0c;SpringBoot除了做了依赖管理&#xff0c;还帮我们做了一些常规的配置&#xff0c;下面举例详细说明。 tomcat 项目可以直接启动&#xff0c;不需要任何配置&#xff0c;通过上一节发现引入了启动器中引入了spring-boot-st…

Python之数据分析基础

导言&#xff1a; “21世纪的竞争是数据的竞争&#xff0c;谁掌握数据&#xff0c;谁就掌握未来”。如何将大量看似杂乱无章的数据进行聚合&#xff0c;并发现潜在的规律也变得越来越重要。本文将先说明数据分析的步骤&#xff0c;再通过python完成实例数据的处理、分析最终展…

【初阶数据结构】顺序表OJ题讲解

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录与初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

PyTorch 图像篇

计算机视觉技术是一门包括计算机科学与工程、神经生理学、物理学、信号处理、认知科学、应用数学与统计等多学科的综合性科学技术&#xff0c; 是人工智能的一个重要分支&#xff0c; 目前在智能安防、自动驾驶汽车、医疗保健、生成制造等领域具有重要的应用价值。 计算机视觉…

常用控件(一)

常用控件 一 按钮类控件QPushButtonQRadioButtonQCheckBox 按钮类控件 QPushButton 使用QPushButton表示一个按钮&#xff0c;这是我们当前最熟悉的一个控件了; QPushButton继承自QAbstractButton&#xff0c;这个类是个抽象类&#xff0c;是其他按钮类的父类; QAbstractButt…

anaconda虚拟环境pytorch安装

1.先创建conda的虚拟环境 conda create -n gputorch python3.102.激活刚刚创建好的虚拟环境 conda activate gputorch3.设置镜像源 这一步是后面安装pytorch相关包所需要的来源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple4.查看电脑的显卡…

跨界内容营销:Kompas.ai如何帮助你的品牌打破行业边界

在当今多元化的市场环境中&#xff0c;跨界营销已成为品牌拓展影响力和用户基础的重要策略。通过跨界合作&#xff0c;品牌能够打破行业界限&#xff0c;创造独特的用户体验&#xff0c;从而提升品牌形象和市场竞争力。本文将深入分析跨界营销的作用&#xff0c;详细介绍Kompas…

SliderCaptcha滑块验证码功能

SliderCaptcha滑块验证码功能 资源文件及文档&#xff1a;https://gitee.com/LongbowEnterprise/SliderCaptcha <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><…

mysql 其他类型转换为BIT

看官网说明,BIT没什么特殊之处。但实际操作却不能将任何其他类型字段转为BIT,下面两个都报语法错误 CAST(column AS BIT(1)) AS aa , CAST(column AS BIT) AS bb, BIT value则模式是VARBINARY b1 as cc, -- cc为VARBINARY类型 下面是《高性能MySQL(第四版)》中关于BIT类型的…

cPanel中如何卸载已安装的SSL证书

我使用的Hostease的Linux虚拟主机产品默认带普通用户权限的cPanel面板&#xff0c;由于临时搭建了一个测试的个人的纯静态的网站&#xff0c;不想要安装SSL证书&#xff0c;但是据这边了解HosteaseLinux虚拟主机是只要域名解析指向主机IP&#xff0c;并且绑定到主机&#xff0c…

centos7.9升级4.19内核

centos默认的内核版本是3.10 通过命令 uname -a 输出系统的详细信息 在部署k8s集群时使用默认的3.10版本的内核&#xff0c;容易出各种奇奇怪怪的问题、可以理解为docker和k8s与该内核版本不兼容&#xff0c;所以在部署k8s集群时&#xff0c;务必要升级内核&#xff0c;这里…

引入RabbitMQ

前置条件 docker 安装 mq docker run \-e RABBITMQ_DEFAULT_USERdudu \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5672:5672 \--network hmall \-d \rabbitmq:3.8-management可能会出现&#xff1a;docker: Er…

福昕PDF阅读器取消手型工具鼠标点击翻页

前言&#xff1a; 本文介绍如何关闭福昕PDF阅读器取消手型工具鼠标点击翻页&#xff0c;因为这样真的很容易误触发PDF翻页&#xff0c;使用起来让人窝火。 引用&#xff1a; NA 正文&#xff1a; 新版的福昕PDF阅读器默认打开了“使用手型工具阅读文章”这个勾选项&#x…

五、Redis五种常用数据结构-SET

Redis的Set结构存储的数据和Java中的HashSet类似&#xff0c;都是无序且不重复的。其底层的数据结构有两种&#xff0c;一是当value为整数时&#xff0c;且数据量不大时采用intset来存储。其他情况使用dict字典存储。集合中最多存储232-1(40多亿)个数据。 1、常用命令 sadd k…