双链表的实现

news2024/11/17 23:44:45

我们知道链表其实有很多种,什么带头,什么双向啊,我们今天来介绍双向带头循环链表,了解了这个其他种类的链表就很简单了。冲冲冲!!!

链表的简单分类

链表有很多种,什么带头循环链表,我们先看这一个图就十分了解了。

我们只需要知道链表的底层逻辑,那么不管什么链表都能说出个大概。

双链表的简要概述

带头双向循环链表(以下都称双链表)和顺序表一样它是一个连着一个,但有一个哨兵位(充当头节点,注意里面没有有效数据),它的特点通俗的讲每个节点都能找到邻位,这个好处是循环用的少。我们带图来理解一下。

双链表的实现

我们创建一个List.h文件放双链表增删查改函数的声明,一个List.c文件放增删查改函数的实现。而链表节点的声明:

typedef int LDataType;
//双链表节点
typedef struct ListNode
{
	LDataType x;
	struct ListNode* prev;
	struct ListNode* next;
}LNode;

初始化函数的实现

这个函数的作用是创建一个哨兵位节点,以后的增删查改函数都基于这个哨兵位上面。既然是要创建节点,那么我们得先开辟一个节点空间,不如我们将开辟空间封装成一个函数,后续开辟空间调用这个函数即可。

//链表节点申请
LNode* LBuyNode(LDataType  x)
{
	LNode* node = (LNode*)malloc(sizeof(LNode));
	if (node == NULL)
	{
		perror("LBuyNode::malloc!");
		return NULL;
	}
	node->x = x;
	node->prev = node;
	node->next = node;
	return node;
}
//链表的初始化
void InitList(LNode** pphead)
{
	//将第一个节点设为哨兵位
	*pphead = LBuyNode(-1);
}

尾插函数的实现

在尾插之前我们首先要开辟节点然后再插入,进行一系列操作。

//尾插
void LpushBack(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = LBuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

打印函数的实现

注意我们这个函数的打印和顺序表,单链表打印完全不同,稍不注意就会陷入死循环。

//链表打印
void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("\n");
}

头插函数的实现

和尾插函数一样,插入之前要先开辟节点空间再进行一系列操作。

//头插
void LpushFront(LNode* phead, LDataType x)
{
	//插入之前哨兵位不能没有哨兵位
	assert(phead);
	LNode* newnode = LBuyNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

查找函数的实现

这个函数还是和打印函数一样,注意别陷入死循环。

//查找某个值
LNode* LFind(LNode* phead, LDataType x)
{
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

在指定位置之后插入函数的实现

这个函数一般配合上面的查找函数一起合作完成。

//在pos位置之后插入
void InsertAfter(LNode* pos, LDataType x)
{
	//插入的位置不能为空
	assert(pos);
	LNode* newnode = LBuyNode(x);
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
	newnode->prev = pos;
}

在指定位置之前插入函数的实现

这个函数仍然要和查找函数配合完成。

//在pos位置之前插入
void InsertBefore(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = LBuyNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}

尾删函数的实现

//尾删
void LpopBack(LNode* phead)
{
	assert(phead);
	LNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

头删函数的实现

这个函数还是和上面尾删函数没啥特点,我们自己想几分钟就行。这里不过多介绍。

//头删
void LpopBefore(LNode* phead)
{
	assert(phead);
	LNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}

删除指定位置节点

//删除pos节点
void LDelpos(LNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

链表销毁函数的实现

这里销毁函数和其他链表函数销毁不同,这里注意区别,防止陷入死循环。

//链表的销毁
void LDestory(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur!=phead)
	{
		free(cur);
		cur = cur->next;
	}
	cur = NULL;
	free(phead);
	phead = NULL;
}

总代码

List.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LDataType;
//双链表节点
typedef struct ListNode
{
	LDataType x;
	struct ListNode* prev;
	struct ListNode* next;
}LNode;

//链表的初始化
void InitList(LNode** pphead);

//链表节点申请
LNode* LBuyNode(LDataType  x);

//头插
void LpushFront(LNode* phead, LDataType x);

//链表打印
void LPrint(LNode* phead);

//尾插
void LpushBack(LNode* phead, LDataType x);

//查找某个值
LNode* LFind(LNode* phead, LDataType x);

//在pos位置之后插入
void InsertAfter(LNode* pos, LDataType x);

//在pos位置之前插入
void InsertBefore(LNode* pos, LDataType x);

//尾删
void LpopBack(LNode* phead);

//头删
void LpopBefore(LNode* phead);

//删除pos节点
void LDelpos(LNode* pos);

//链表的销毁
void LDestory(LNode* phead);

List.c文件

#include"List.h"
//链表节点申请
LNode* LBuyNode(LDataType  x)
{
	LNode* node = (LNode*)malloc(sizeof(LNode));
	if (node == NULL)
	{
		perror("LBuyNode::malloc!");
		return NULL;
	}
	node->x = x;
	node->prev = node;
	node->next = node;
	return node;
}
//链表的初始化
void InitList(LNode** pphead)
{
	//将第一个节点设为哨兵位
	*pphead = LBuyNode(-1);
}
//链表打印
void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("\n");
}
//头插
void LpushFront(LNode* phead, LDataType x)
{
	//插入之前哨兵位不能没有哨兵位
	assert(phead);
	LNode* newnode = LBuyNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}
//尾插
void LpushBack(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = LBuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}
//查找某个值
LNode* LFind(LNode* phead, LDataType x)
{
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos位置之后插入
void InsertAfter(LNode* pos, LDataType x)
{
	//插入的位置不能为空
	assert(pos);
	LNode* newnode = LBuyNode(x);
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
	newnode->prev = pos;
}
//在pos位置之前插入
void InsertBefore(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = LBuyNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}
//尾删
void LpopBack(LNode* phead)
{
	assert(phead);
	LNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}
//头删
void LpopBefore(LNode* phead)
{
	assert(phead);
	LNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}
//删除pos节点
void LDelpos(LNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}
//链表的销毁
void LDestory(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur!=phead)
	{
		free(cur);
		cur = cur->next;
	}
	cur = NULL;
	free(phead);
	phead = NULL;
}

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

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

相关文章

tcp-learner 数据包分析 20240420

输入输出&#xff1a; 数据包分析&#xff1a; learner和Adapter建立连接。 Learner让Adapter发送RST Adapter没有从SUT抓到任何回复&#xff0c;于是向learner发送timeout learner给adapter发送reset命令&#xff0c;让SUT重置。 这是第一次初始化&#xff0c;由于Adapter和…

Spring Boot后端与Vue前端融合:构建高效旅游管理系统

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

【简单讲解下npm常用命令】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【hackmyVM】whitedoor靶机

文章目录 信息收集1.IP地址2.端口探测nmapftp服务 3.访问主页 漏洞利用1.反弹shell2.尝试提权3.base64解密 提权1.切换用户2.john爆破3.切换Gonzalo用户4.vim提权 信息收集 1.IP地址 ┌─[✗]─[userparrot]─[~] └──╼ $fping -ag 192.168.9.0/24 2> /dev/null192.168…

ZYNQ NVME高速存储之EXT4文件系统

前面文章分析了高速存储的各种方案&#xff0c;目前主流的三种存储方案是&#xff0c;pcie switch高速存储方案&#xff0c;zynq高速存储方案&#xff0c;fpga高速存储方案。虽然三种高速存储方案都可以实现高速存储&#xff0c;但是fpga高速存储方案是最烂的&#xff0c;fpga…

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决 文章目录 Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决一、前言二、分析解决1、原因分析2、解决方法 三、其他1、Android13 新项目无法编…

什么是时间序列分析

时间序列分析是现代计量经济学的重要内容&#xff0c;广泛应用于经济、商业、社会问题研究中&#xff0c;在指标预测中具有重要地位&#xff0c;是研究统计指标动态特征和周期特征及相关关系的重要方法。 一、基本概念 经济社会现象随着时间的推移留下运行轨迹&#xff0c;按…

随身WiFi真实测评推荐!格行vs新讯随身wifi对比,公认最好的随身WiFi格行随身wifi有什么优势?

在当前移动网络高度发达的时代&#xff0c;随身 WiFi 已成为人们出差、旅行等场景中不可或缺的工具。格行和新讯是目前比较受欢迎的无线随身wifi。本次评测将对比分析这两款产品的区别&#xff0c;做为随身WiFi推荐第一名的格行随身wifi到底有什么优势呢&#xff1f; 品牌对比&…

[阅读笔记15][Orca]Progressive Learning from Complex Explanation Traces of GPT-4

接下来是微软的Orca这篇论文&#xff0c;23年6月挂到了arxiv上。 目前利用大模型输出来训练小模型的研究都是在模仿&#xff0c;它们倾向于学习大模型的风格而不是它们的推理过程&#xff0c;这导致这些小模型的质量不高。Orca是一个有13B参数的小模型&#xff0c;它可以学习到…

C++ 内存分区管理

一、栈区&#xff08;Stack&#xff09; 栈区用来存储函数的参数值、局部变量的值等数据。栈区是自动分配和释放的&#xff0c;函数执行时会在栈区分配空间&#xff0c;函数执行结束时会自动释放这些空间。栈区的数据是连续分配的&#xff0c;由系统自动管理。 注意事项&…

layui框架实战案例(27):弹出二次验证

HTML容器 <button class"layui-btn layui-btn-sm layui-btn-danger" lay-event"delete"><i class"layui-icon layui-icon-delete"></i>批量删除</button>删除封装函数 function delAll(school_id, school_name) {var lo…

牛x之路 - Day1

Day1 微积分之屠龙宝刀&#xff08;武林秘籍&#xff09; 之前的一些东西都在pdf上记得笔记&#xff0c; 没有在这个上面展示一遍&#xff0c;只好学到相关内容的时候再提叙啦&#xff1b;所以其实再写这个小记的时候&#xff0c;我已经看了一半的书&#xff0c;但是不要紧&am…

每日学习笔记:C++ STL算法之移除容器元素

本文API 移除元素 remove(beg, end, value) remove_if(beg, end, op) remove_copy(sourceBeg, sourceEnd, destBeg, value) remove_copy_if(sourceBeg, sourceEnd, destBeg, op) 移除连续重复的元素 unique(beg, end) unique(beg, end, op) unique_copy(sourceBeg, sourceEnd, …

Ribbon 添加快速访问区域

添加快速访问区域挺简单的&#xff0c;实例如下所示&#xff1a; void QtRightFuncDemo::createQuickAccessBar() { RibbonQuickAccessBar* quickAccessBar ribbonBar()->quickAccessBar(); QAction* action quickAccessBar->actionCustomizeButton(); act…

单链表的简单应用

目录 一、顺序表的问题及思考 二、链表的概念及结构 三、单链表的实现 3.1 增 3.1.1 尾插 3.1.2 头插 3.1.3 指定位置前插入 3.1.4 指定位置后插入 3.2 删 3.2.1 尾删 3.2.2 头删 3.2.3 指定位置删除 3.2.4 指定位置后删除 3.2.5 链表的销毁 3.3 查 3.4 改 四…

Python爬虫使用需要注意什么?应用前景如何?

Python爬虫很多人都听说过&#xff0c;它是一种用于从网页上获取信息的程序&#xff0c;它可以自动浏览网页、提取数据并进行处理。技术在使用Python爬虫时需要注意一些重要的事项&#xff0c;同时本文也会跟大家介绍一下爬虫的应用前景。 第一个注意事项就是使用Python爬虫时…

HCIP-OSPF综合实验

一实验拓扑图 二.实验要求 1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&…

【JavaEE多线程】线程安全、锁机制及线程间通信

目录 线程安全线程安全问题的原因 synchronized 关键字-监视器锁monitor locksynchronized的特性互斥刷新内存可重入 synchronized使用范例 volatilevolatile能保证内存可见性volatile不保证原子性synchronized 也能保证内存可见性 wait 和 notifywait()方法notify()方法notify…

【CBB系列】EtherCAT硬件技术总结及其从站硬件设计

EtherCAT硬件技术总结及其从站硬件设计 EtherCAT硬件技术简介基于LAN9252的EtherCAT从站硬件设计LAN9252总览电源、时钟与复位主机总线(PDI/SPI)与MIII2C接口与硬配置引脚LED控制器与PORT总结作者按:最近在《硬件十万个为什么-开发流程篇》中看到了共用基础模块(Common bui…

最前沿・量子退火建模方法(2) : Domain wall encoding讲解和python实现

前言 上篇讲的subQUBO属于方法论&#xff0c;这次讲个通过编码量子比特的方式&#xff0c;同样的约束条件&#xff0c;不同的编码&#xff0c;所需的量子比特数是不同的。有的编码方式&#xff0c;很节省量子比特。比如&#xff0c;这次要讲的Domain wall encoding。 一、Doma…