【C语言】实现双向链表


目录

(一)头文件

(二) 功能实现

(1)初始化

 (2)打印链表

(3) 头插与头删

(4)尾插与尾删

(5)指定位置之后插入

(6)删除指定位置的数据

(7)链表的销毁


 

正文开始:

        在实际应用中,常用的双向链表是双向带头循环链表,本文考虑到实际应用,目的也是实现双向带头循环链表。

(一)头文件

        命名"List.h"

        本文不加解释的给出头文件,按照头文件来实现双向链表的基本功能:包括打印链表,链表的初始化,头插与头删,尾插与尾删,在指定位置之后插入数据,删除指定位置的数据,链表的销毁等共九个功能。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表的数据类型
typedef int LTtype;
//双向链表的节点
typedef struct ListNode
{
	LTtype data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//双向链表带有哨兵位,插入数据之前先插入哨兵位
//初始化》传入头节点
void LTInit(LTNode** pphead);


//初始化2>返回头节点
LTNode* LtInit0();

//销毁链表
void LTDestory(LTNode** pphead);

//尾插
void LTtail_push(LTNode* phead,LTtype x);

//头插
//是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead,LTtype x);

//打印便于调试
void Print(LTNode* phead);

//头删
void LTpophead(LTNode* phead);

//尾删
void LTpoptail(LTNode* phead);

//查找
//为了找到pos位置
LTNode* LTfind(LTNode* phead, LTtype x);

//在pos之后插入数据
void LTinsert(LTNode* pos, LTtype x);

//删除pos处的数据
void LTerase(LTNode* pos);

(二) 功能实现

        带头相对于不带头有诸多优势:

带头与不带头链表:

        带头链表是指在链表的开头增加一个额外的节点,该节点不存储具体的数据,仅用于辅助操作链表。带头链表的带头节点可以简化链表的操作,提高代码的可读性和可维护性。

        意义:

        带头节点可以避免链表为空的特殊情况处理。在没有带头节点的链表中,如果链表为空,添加、删除等操作都需要额外的处理逻辑。而带头节点的链表中,带头节点一直存在,无论链表是否为空,操作逻辑都是一致的,可以减少代码的复杂性。

        局限:

        造成额外的空间浪费。

(1)初始化

         初始化有两种方法,具体实现如下:

传址初始化

        初始化传递链表的地址(二级指针【通过传址,将形参与实参建立联系】),初始化要插入头指针哨兵位(不保存有效的数据),便于简化代码逻辑。

//双向链表带有哨兵位,插入数据之前先插入哨兵位
void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	if (*pphead == NULL)
	{
		perror("malloc");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;
}

接受返回值初始化

        在函数内申请头节点,最后返回到主函数内:

//初始化2>返回头节点
LTNode* LtInit0()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		exit(1);
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

 (2)打印链表

        以便于调试,理解代码;

        与打印单链表一样,不同的是:while的跳出条件是 pcur != phead;


//打印链表
void Print(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

(3) 头插与头删

        插入操作需要申请新节点:


//获取新节点
LTNode* LTbuynewnode(LTtype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = NULL;
	return newnode;
}

 

        头插与头删

         头插,是在头节点之后插入,在头节点之前插入等价于尾插;

         先对新节点进行操作,因为新节点与原节点没有联系,不会因为对指针进行操作而丢失节点,造成数据丢失和内存泄漏;

        我们可以直接得到phead,因此先让phead的下一个节点的前驱指针指向newnode,再改变phead的next指针;(如果反过来,先改变phead的next指向newnode,此时newnode后面的节点就找不到了。)


//头插,是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead, LTtype x)
{
	assert(phead);

	LTNode* newnode = LTbuynewnode(x);

    //先对新节点进行操作
	newnode->next = phead->next;
	newnode->prev = phead;


	phead->next->prev = newnode;
	phead->next = newnode;

}

头删

        断言传过来的指针不为空,链表不为空,通过assert实现;

        通过创建del指针(要被删除的节点),保存旧头节点;

        通过创建next指针,保存旧头节点的next节点;

        这样命名后进行头删,逻辑清晰,不易出错;


//头删
void LTpophead(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* del = phead->next;//被删除的节点
	LTNode* next = del->next;//del的后置节点

	phead->next = next;
	next->prev = phead;

	free(del);
	del = NULL;
}

 

(4)尾插与尾删

尾插

        与头插的逻辑完全相同


//尾插
void LTtail_push(LTNode* phead, LTtype x)
{
	assert(phead);

	LTNode* newnode = LTbuynewnode(x);

	newnode->next = phead;
	newnode->prev = phead->prev;

	phead->prev->next = newnode;
	phead->prev = newnode;
}

尾删

        与头删的逻辑完全相同


//尾删
void LTpoptail(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* del = phead->prev;//被删除的节点
	LTNode* prev = del->prev;//del的前置节点

	prev->next = phead;
	phead->prev = prev;

	free(del);
	del = NULL;
}

 

(5)指定位置之后插入

查找

         查找数据值为x的节点,找到返回此节点,找不到返回NULL;


//查找
LTNode* LTfind(LTNode* phead,LTtype x)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

 

 在指定位置之后插入数据

        根据find找到的pos位置,申请新节点,插入到pos之后


//在pos位置之后插入数据
void LTinsert(LTNode* pos,LTtype x)
{
	assert(pos);

	LTNode* newnode = LTbuynewnode(x);
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;
}

 

(6)删除指定位置的数据

         删除指定位置的数据,需要的指针有pos的前驱节点,pos节点。


//删除pos处的数据
void LTerase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

 

(7)链表的销毁

        链表的销毁共有两种方法:

传址销毁


//销毁链表
void LTDestory(LTNode** pphead)
{
	assert(pphead);
	//哨兵位不为空
	assert(*pphead);

	LTNode* pcur = (*pphead)->next;

	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//释放哨兵位
	free(pphead);
	//此时可以改变哨兵位的
	pphead = NULL;

}

 传值销毁

        一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
        函数返回后,仍需要手动phead = NULL;


//推荐使用一级,为了保持接口的一致性
void LTDestory0(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;//无效的置空,当做是习惯
}
//一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
//函数返回后,仍需要手动phead = NULL;

 

完~

未经作者同意禁止转载 

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

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

相关文章

html的格式化标签和图片(img)标签

格式化标签 加粗: strong标签和b标签倾斜: em标签和i标签删除线: del标签和s标签下划线: ins标签和u标签 <strong>stong 加粗</strong><b>b 加粗</b><em>倾斜</em><i>倾斜</i><del>删除线</del><s>删除线…

nodejs学习计划--(十)会话控制及https补充

一、会话控制 1.介绍 所谓会话控制就是 对会话进行控制 HTTP 是一种无状态的协议&#xff0c;它没有办法区分多次的请求是否来自于同一个客户端&#xff0c; 无法区分用户 而产品中又大量存在的这样的需求&#xff0c;所以我们需要通过 会话控制 来解决该问题 常见的会话控制…

论文阅读:GamutMLP A Lightweight MLP for Color Loss Recovery

这篇文章是关于色彩恢复的一项工作&#xff0c;发表在 CVPR2023&#xff0c;其中之一的作者是 Michael S. Brown&#xff0c;这个老师是加拿大 York 大学的&#xff0c;也是 ISP 领域的大牛&#xff0c;现在好像也在三星研究院担任兼职&#xff0c;这个老师做了很多这种类似的工…

苹果Mac键盘如何将 F1 到 F12 取消按Fn

苹果电脑安装了Win10操作系统之后&#xff0c;F1到F12用不了怎么办的解决方法。本文将介绍一些解决方法&#xff0c;帮助您解决无法使用F1到F12功能键的问题。 使用 Mac系统的人都知道&#xff0c;Mac系统默认是没有开启 F1-F12 的使用的&#xff0c;平时我们使用的系统都可以使…

线性时间非比较类排序之基数排序

基数排序 基数排序是桶排序的扩展&#xff0c;因此又称“桶子法”&#xff0c;它是通过键值的部分信息&#xff0c;将要排序的元素分配至某些“桶”中&#xff0c;以达到排序的作用。 1. 算法思想 将各元素按位数切割成不同的数字&#xff0c;然后分别根据每个位数的比较结果…

在线问诊系统设计与实现的经验总结与整理

随着互联网技术的快速发展&#xff0c;在线问诊服务作为一种新兴的医疗服务模式&#xff0c;正逐渐受到人们的关注和使用。本文将介绍在线问诊系统的设计原则和关键组件&#xff0c;以及如何实现一个安全、高效和可扩展的在线医疗服务平台。 内容&#xff1a; 1. 引言 - 在…

Vulnhub靶场 DC-8

目录 一、环境搭建 二、信息收集 1、主机发现 2、指纹识别 三、漏洞复现 1、SQL注入 sqlmap工具 2、dirsearch目录探测 3、反弹shell 4、提权 exim4 5、获取flag 四、总结 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a;https://download.vulnhub.com/dc/DC-…

Python算法题集_合并K个升序链表

Python算法题集_合并K个升序链表 题23&#xff1a;合并K个升序链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【列表排序】3) 改进版二【堆排序】4) 改进版三【分区海选】 4. 最优算法 本文为Python算法题集之一的代…

论文介绍 FreeControl: 无需额外训练实现文本到图像的空间操控!

论文介绍 FreeControl: 无需额外训练实现文本到图像的空间操控&#xff01; 论文介绍 FreeControl: Training-Free Spatial Control of Any Text-to-Image Diffusion Model with Any Condition 关注微信公众号: DeepGo 项目地址&#xff1a;https://genforce.github.io/freeco…

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统(有论文)

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的超市进销存系统 本系统分为登录注册模块、管理员功能模块以及员工功能模块。 登录注册模块&#…

Java学习18-- Override方法重写【★】

重点&#xff1a;super类 & 方法重写 ★看不明白多看几遍&#xff0c;记住static优先级>>高于override 重写Override methods★ 重写Override&#xff1a;child class可以覆盖father class中的method&#xff0c;即子类child class和父类father class有相同名称、…

如何部署一个高可用的 Linux 集群?

部署一个高可用的 Linux 集群需要经过多个步骤和考虑因素。以下是一个简要的指南&#xff0c;帮助您了解如何部署一个高可用的 Linux 集群&#xff1a; 确定需求和目标&#xff1a;在开始部署之前&#xff0c;您需要明确高可用性的定义和目标。对于一些组织而言&#xff0c;高…

鸿蒙开发第3篇__大数据量的列表加载性能优化

列表 是最常用到的组件 一 ForEach 渲染控制语法————Foreach Foreach的作用 遍历数组项&#xff0c;并创建相同的布局组件块在组件加载时&#xff0c; 将数组内容数据全部创建对应的组件内容&#xff0c; 渲染到页面上 const swiperImage: Resource[] {$r("app.me…

2024春晚纸牌魔术原理----环形链表的约瑟夫问题

一.题目及剖析 https://www.nowcoder.com/practice/41c399fdb6004b31a6cbb047c641ed8a?tabnote 这道题涉及到数学原理,有一般公式,但我们先不用公式,看看如何用链表模拟出这一过程 二.思路引入 思路很简单,就试创建一个单向循环链表,然后模拟报数,删去对应的节点 三.代码引…

数据库管理-第150期 Oracle Vector DB AI-02(20240212)

数据库管理150期 2024-02-12 数据库管理-第150期 Oracle Vector DB & AI-02&#xff08;20240212&#xff09;1 LLM2 LLM面临的挑战3 RAG4 向量数据库LLM总结 数据库管理-第150期 Oracle Vector DB & AI-02&#xff08;20240212&#xff09; 作者&#xff1a;胖头鱼的鱼…

LeetCode:69.x的平方根

嗨嗨嗨&#xff0c;二分又来了&#xff0c;淦它&#xff0c; 这个题官解是&#xff0c;C函数法&#xff0c;二分&#xff0c;和牛顿迭代法&#xff08;暂且搁置&#xff09;&#xff0c; 当然还有暴力&#xff08;不必讨论&#xff0c;就从0开始一个一个试&#xff09;&#…

2.11日学习打卡----初学RocketMQ(二)

2.11日学习打卡 一. RocketMQ整合springboot 首先配置pom.xml文件 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>annotationProcessor</scope></dependency><dependency>…

服装效果图为何要用云渲染100?渲染100邀请码1a12

服装行业是充满创意和竞争的领域&#xff0c;而服装效果图是其中重要一环&#xff0c;以前效果图都是本地渲染&#xff0c;现在越来越多的设计师转向云渲染&#xff0c;以国内最专业的平台渲染100为例&#xff0c;云渲染有以下好处&#xff1a; 1、提高工作效率 设计师可以利用…

Netty源码系列 之 FastThreadLocal源码

目录 Netty优化方案之 FastThreadLocal 前言 ThreadLocal ThreadLocal是干什么的&#xff1f; 为什么要使用ThreadLocal工具类去操控存取目标数据到Thread线程 &#xff1f; ThreadLocal的使用场景 目标数据存储到Thread线程对象的哪里&#xff1f; 怎么样把一个目标数据…

JavaWeb:SpingBoot原理 --黑马笔记

1. 配置优先级 在我们前面的课程当中&#xff0c;我们已经讲解了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;可以通过这三种方式当中…