【数据结构】带头双向循环链表的实现及链表顺序表的区别

news2024/12/24 0:35:52

目录

一、带头双向循环链表接口实现

连接关系:

创建哨兵位(表头):

头插——头删:

尾插——尾删:

查找——打印:

指定位置pos前插入,删除pos位置:

链表销毁:

二、快速实现“链表”

三、链表和顺序表的区别:

四、整体代码:

一、带头双向循环链表接口实现

连接关系:

实现这个链表最重要的就是理清楚连接关系:这里我给出两个动图,模拟这个关系

插入:

删除:

创建哨兵位(表头):

这里我们可以用两种方法,一种是采取二级指针的方法,另一种是直接创建一个表头,然后把表头地址返回去,这里我们采用第二种:

//创建新节点
ListNode* CreateNewNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->_data = x;
	newnode->_next = NULL;
	newnode->_prev = NULL;
	return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = CreateNewNode(-1);
	head->_prev = head;
	head->_next = head;
	return head;
}

头插——头删:

头插:

头插的时候,我们保存好链表第一个有效节点的地址,然后插入,记得把连接关系连接清楚

头删:

头删的时候,记得保存好,第二个节点的地址,记得把表头与第二节点的连接关系连接清楚

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreateNewNode(x);
	ListNode* second = pHead->_next;
	pHead->_next = newnode;
	newnode->_prev = pHead;
	newnode->_next = second;
	second->_prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->_next != pHead);
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;
	pHead->_next = second;
	second->_prev = pHead;
	free(first);
	first = NULL;
}

尾插——尾删:

尾插:

保存好当前尾节点的地址,注意搞清楚头节点,尾节点,新节点的连接关系

尾删:

保存好最后两个节点的位置,注意连接清楚头节点和倒数第二个节点

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode= CreateNewNode(x);
	ListNode* tail = pHead->_prev;
	newnode->_next = pHead;
	newnode->_prev = tail;
	tail->_next = newnode;
	pHead->_prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->_next != pHead);
	ListNode* tail = pHead->_prev;
	ListNode* tailPrev = tail->_prev;
	pHead->_prev = tailPrev;
	tailPrev->_next = pHead;
	free(tail);
	tail = NULL;
}

查找——打印:

注意:

循环从哨兵位的下一个节点开始,直到等于哨兵位结束

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
			return cur;
		cur = cur->_next;
	}
	return NULL;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur!=pHead)
	{
		printf("%d <-> ", cur->_data);
		cur = cur->_next;
	}
	printf("NULL\n");
}

指定位置pos前插入,删除pos位置:

pos前插入: 

保存好pos前一个位置的地址,连接清楚就行了

删除pos位置:

保存好pos前后的位置,连接清楚就行了

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = CreateNewNode(x);
	ListNode* prev = pos->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pos;
	pos->_prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->_prev;
	ListNode* next = pos->_next;
	prev->_next = next;
	next->_prev = prev;
	free(pos);
	pos = NULL;
}

链表销毁:

这一个要从哨兵位的下一个节点开始,循环直到不等于哨兵位结束:

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

二、快速实现“链表”

学了带头双向循环链表之后,我们可以快速实现一个简单链表:

大多数接口其实都是不变的,但是有4个接口可以简化,那就是头插——头删——尾插——尾删

这两个接口可以复用insert接口和erase接口:这四个接口连接关系是比较麻烦的,省去了这几个麻烦的,其他的接口就会很快写出来,大大节省了时间:

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListNode* newnode= CreateNewNode(x);
	//ListNode* tail = pHead->_prev;
	//newnode->_next = pHead;
	//newnode->_prev = tail;
	//tail->_next = newnode;
	//pHead->_prev = newnode;

	ListInsert(pHead,x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//assert(pHead->_next != pHead);
	//ListNode* tail = pHead->_prev;
	//ListNode* tailPrev = tail->_prev;
	//pHead->_prev = tailPrev;
	//tailPrev->_next = pHead;
	//free(tail);
	//tail = NULL;

	ListErase(pHead->_prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListNode* newnode = CreateNewNode(x);
	//ListNode* second = pHead->_next;
	//pHead->_next = newnode;
	//newnode->_prev = pHead;
	//newnode->_next = second;
	//second->_prev = newnode;

	ListInsert(pHead->_next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//assert(pHead->_next != pHead);
	//ListNode* first = pHead->_next;
	//ListNode* second = first->_next;
	//pHead->_next = second;
	//second->_prev = pHead;
	//free(first);
	//first = NULL;

	ListErase(pHead->_next);
}

三、链表和顺序表的区别:

顺序表空间连续支持下标访问使得我们在排序,访问等很多场景下都是很高效的

链表的合理利用空间使我们对空间的管理更合理

四、整体代码:

SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//创建新节点
ListNode* CreateNewNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->_data = x;
	newnode->_next = NULL;
	newnode->_prev = NULL;
	return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = CreateNewNode(-1);
	head->_prev = head;
	head->_next = head;
	return head;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur!=pHead)
	{
		printf("%d <-> ", cur->_data);
		cur = cur->_next;
	}
	printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListNode* newnode= CreateNewNode(x);
	//ListNode* tail = pHead->_prev;
	//newnode->_next = pHead;
	//newnode->_prev = tail;
	//tail->_next = newnode;
	//pHead->_prev = newnode;

	ListInsert(pHead,x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//assert(pHead->_next != pHead);
	//ListNode* tail = pHead->_prev;
	//ListNode* tailPrev = tail->_prev;
	//pHead->_prev = tailPrev;
	//tailPrev->_next = pHead;
	//free(tail);
	//tail = NULL;

	ListErase(pHead->_prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListNode* newnode = CreateNewNode(x);
	//ListNode* second = pHead->_next;
	//pHead->_next = newnode;
	//newnode->_prev = pHead;
	//newnode->_next = second;
	//second->_prev = newnode;

	ListInsert(pHead->_next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//assert(pHead->_next != pHead);
	//ListNode* first = pHead->_next;
	//ListNode* second = first->_next;
	//pHead->_next = second;
	//second->_prev = pHead;
	//free(first);
	//first = NULL;

	ListErase(pHead->_next);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
			return cur;
		cur = cur->_next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = CreateNewNode(x);
	ListNode* prev = pos->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pos;
	pos->_prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->_prev;
	ListNode* next = pos->_next;
	prev->_next = next;
	next->_prev = prev;
	free(pos);
	pos = NULL;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void test()
{
	ListNode* plist = ListCreate();

	ListPushBack(plist,1);
	ListPushBack(plist,2);
	ListPushBack(plist,3);
	ListPushBack(plist,4);
	ListPushBack(plist,5);

	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);

	ListPrint(plist);

	ListPushFront(plist,6);
	ListPushFront(plist,7);
	ListPushFront(plist,8);
	ListPushFront(plist,9);
	ListPushFront(plist,10);

	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPopFront(plist);

	ListPrint(plist);

	printf("%d\n", ListFind(plist, 6)->_data);

	ListPrint(plist);

	ListInsert(ListFind(plist,6), 99);
	ListInsert(ListFind(plist,7), 88);

	ListPrint(plist);

	ListErase(ListFind(plist, 99));
	ListErase(ListFind(plist, 88));

	ListPrint(plist);


	ListDestory(plist);
	plist = NULL;
}
int main()
{
	test();

	return 0;
}

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

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

相关文章

springboot解析自定义yml文件

背景 公司产品微服务架构下有十几个模块&#xff0c;几乎大部分模块都要连接redis。每次在客户那里部署应用&#xff0c;都要改十几遍配置&#xff0c;太痛苦了。当然可以用nacos配置中心的功能&#xff0c;配置公共参数。不过我是喜欢在应用级别上解决问题&#xff0c;因为并不…

“管式加热炉简单控制系统和串级控制系统设计与Matlab仿真”,高分资源,匠心制作,下载可用。强烈推荐!!!

“管式加热炉简单控制系统和串级控制系统设计与Matlab仿真”毕业设计&#xff0c;高分资源&#xff0c;匠心制作&#xff0c;下载可用。强烈推荐&#xff01;&#xff01;&#xff01; 1.控制目标 加热炉的任务是把原油加热到一定温度&#xff0c;以保证下道工艺的顺利进行。…

【C语言】解决C语言报错:Double Free

文章目录 简介什么是Double FreeDouble Free的常见原因如何检测和调试Double Free解决Double Free的最佳实践详细实例解析示例1&#xff1a;重复调用free函数示例2&#xff1a;多次释放全局变量指针示例3&#xff1a;函数间传递和释放指针 进一步阅读和参考资料总结 简介 Doub…

基于redisson实现tomcat集群session共享

目录 1、环境 2、修改server.xml 3、修改context.xml 4、新增redisson配置文件 5、下载并复制2个Jar包到Tomcat Lib目录中 6、 安装redis 7、配置nginx负载均衡 8、配置测试页面 9、session共享测试验证 前言&#xff1a; 上篇中&#xff0c;Tomcat session复制及ses…

关于等保测评你了解多少?

在当今数字化时代&#xff0c;网络安全问题愈发凸显其重要性。作为保障信息系统安全的关键环节&#xff0c;等保测评&#xff08;网络安全等级保护测评&#xff09;成为了企业和组织不可或缺的一部分。那么&#xff0c;关于等保测评&#xff0c;我们究竟了解多少呢&#xff1f;…

基于STM32的智能家庭安防系统

目录 引言环境准备智能家庭安防系统基础代码实现&#xff1a;实现智能家庭安防系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;家庭安防管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家庭安防系统通过使用ST…

鸿蒙系统最简单安装谷歌服务及软件的方法

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 近日&#xff0c;华为开发者大会在东莞松山湖召开&#xff0c;发布了盘古大模型5.0和纯血版的鸿蒙 HarmonyOS NEXT 全场景智能操作系统&#xff0c;而根据研究机构 Counterpoint Resea…

MySQL进阶_3.MySQL日志

文章目录 第一节、MySQL事务日志1.1、redo日志1.1.1、为什么需要REDO日志1.1.2、REDO日志的好处、特点1.1.3、redo的组成1.1.4、redo的整体流程 1.2、Undo日志1.2.1、如何理解Undo日志1.2.2、Undo日志的作用1.2.3、undo log的生命周期 第一节、MySQL事务日志 事务有4种特性&am…

按位与、或、异或操作符

目录 & --- 按位与操作符 按位与操作符运用规则 按位与操作符相关代码 按位与操作符相关代码验证 | --- 按位或操作符 按位或操作符运用规则 按位或操作符相关代码 按位或操作符相关代码验证 ^ --- 按位异或操作符 按位异或操作符运用规则 按位异或操作符相关代…

24/06/26(1.1129)动态内存

strtok 字符串分割函数 #include<stdio.h> int main(){ char str[] "this,a sample string."; char* sep ","; char* pch strtok(str, sep); printf("%s\n", pch); while (pch ! NULL){ printf("%s\…

LabVIEW中卡尔曼滤波的作用与意义

卡尔曼滤波&#xff08;Kalman Filter&#xff09;是一种在控制系统和信号处理领域广泛应用的递推滤波算法&#xff0c;能够在噪声环境下对动态系统的状态进行最优估计。其广泛应用于导航、目标跟踪、图像处理、经济预测等多个领域。本文将详细介绍卡尔曼滤波在LabVIEW中的作用…

[JS]对象

介绍 对象是一种无序的数据集合, 可以详细的描述某个事物 事物的特征在对象中用属性来表示, 事物的行为在对象中用方法来表示 使用 创建对象 let 对象名 {属性名&#xff1a;值&#xff0c;方法名&#xff1a;函数&#xff0c; } let 对象名 new Object(); 对象名.属性…

深入了解 msvcr120.dll问题解决指南,msvcr120.dll在电脑中的重要性

在Windows操作系统中&#xff0c;.dll 文件扮演了非常重要的角色&#xff0c;它们包含许多程序运行所需的代码和数据。其中 msvcr120.dll 是一个常见的动态链接库文件&#xff0c;是 Microsoft Visual C Redistributable Packages 的一部分。这篇文章将探讨 msvcr120.dll 的功能…

Profibus DP主站转Modbus模块连接马达保护器案例

一、概述 在工业自动化控制系统中&#xff0c;Profibus DP和Modbus是常见的通信协议&#xff0c;在同一现场还有可能遇到Modbus协议&#xff0c;ModbusTCP协议&#xff0c;Profinet协议&#xff0c;Profibus协议&#xff0c;Profibus DP协议&#xff0c;EtherCAT协议&#xff…

appinventor2中求某个值在列表中的索引用什么方法?

使用“求对象在列表中的位置”方法就可以了&#xff1a; 返回指定对象在列表中的位置&#xff0c;从 1 开始&#xff0c;如果不在列表中&#xff0c;则返回 0。 相应地&#xff0c;知道了索引&#xff0c;从列表中取值得方法是&#xff1a;选择列表中索引值对应的列表项 返回…

条码二维码读取设备在医疗设备自助服务的重要性

医疗数字信息化建设的深入推进&#xff0c;医疗设备自助服务系统已成为医疗服务领域的一大趋势&#xff0c;条码二维码读取设备作为自助设备的重要组成部分&#xff0c;通过快速、准确地读取条形码二维码信息&#xff0c;不公提升了医疗服务效率&#xff0c;还为患者提供了更加…

教你如何一键高效下载视频号直播视频

在当今视频号直播盛行的时代&#xff0c;错过精彩直播内容再也不是遗憾&#xff01;地瓜网络技术倾情推出“视频号直播视频下载器”&#xff0c;为您捕捉每一个直播瞬间。本文将简明扼要地指导您如何利用这款神器下载视频号直播与回放视频&#xff0c;让超清MP4视频轻松入库&am…

遇到不可复现的bug要怎么做?

测试人员遇到不可复现的bug要怎么做&#xff1f; 这是一个很常见的问题&#xff0c;也是一个很棘手的问题。不可复现的bug可能会给测试人员带来很大的困扰和压力&#xff0c;因为它们可能会影响软件的质量和用户的体验&#xff0c;但又很难找到问题的根源和解决方法。因此&…

算法与数据结构——时间复杂度详解与示例(C#,C++)

文章目录 1. 算法与数据结构概述2. 时间复杂度基本概念3. 时间复杂度分析方法4. 不同数据结构的时间复杂度示例5. 如何通过算法优化来提高时间复杂度6. C#中的时间复杂度示例7. 总结 算法与数据结构是计算机科学的核心&#xff0c;它们共同决定了程序的性能和效率。在实际开发中…

浅浅谈谈如何利用Javase+多线程+计算机网络的知识做一个爬CSDN阅读量总访问量的程序

目录 我们发现csdn的文章 首先为了印证我们的想法 我们用postman往csdn我们任意一篇文章发起post请求 发送请求 ​编辑获得响应结果 我们发现我们的阅读量上涨 PostRequestSender类 但是我们经过测试发现 定义一个字符串数组 把URL放进去 然后延迟启动 在线程池里面…