【数据结构】——双链表的实现(赋源码)

news2025/1/9 21:37:01

双链表的概念和结构

双链表的全称叫做:带头双向循环链表

它的结构示意图如下

注意:这⾥的“带头”跟前⾯我们说的单链表的“头结点”是两个概念,实际前⾯的在单链表阶段称呼不严谨,但是为了读者们更好的理解就直接称为单链表的头结点。 

带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这⾥“放哨的”也可以认为是用来占位置滴!!!

双链表的实现

首先先在结构体当中输入需要的数据,则有如下的数据是需要的

结构体中的数据

typedef int LTDataType;//方便对数据类型进行统一的替换
typedef struct ListNode ListNode;//对结构体的名称重新命名交ListNode
struct ListNode
{
	LTDataType data;
	ListNode* next;
	ListNode* prev;
};

则上面的图可以变成这样

双链表新结点的创建及双链表的初始化

ListNode* LTBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//一个结构体的大小
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;

	return newnode;//返回头结点
}

链表的初始化需要一个创建的新的结点作为哨兵位

//void LTInit(ListNode** pphead)
//{
//	//创建一个头结点即“哨兵位”
//	*pphead = LTBuyNode(-1);
//}

ListNode* LTInit()
{
	ListNode* phead = LTBuyNode(-1);
	return phead;
}

//上面是两种初始化的方法
//第一种需要传递一个二级指针

在上面的代码当中,我们只需要创建一个头结点来保证第一个“头”存在即可。

插入
第一个参数传一级还是二级,要看phead指向的结点会不会改变
如果发生改变,那么pphead的改变要影响实参,传二级
如果不发生改变,pphead不会影响实参,传一级

双链表的尾插

//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	//创建需要插入的结点
    //上面初始化的newnode是头结点,这个newnode是尾插的结点
	ListNode* newnode = LTBuyNode(x);

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

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

 上面的顺序是不能改变的,否则无法让新结点找到原来链表的位置

这边测试一下我们的尾插代码依次插入1 2 3 4  

 双链表的头插

//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = LTBuyNode(x);

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

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

头插和尾插是类似的 ,不过有一个特殊的地方

头插是头插在哨兵位和第一个真正的结点中间

同样的,上面的顺序位置是不能改变的

测试头插代码

这个代码是在上面尾插代码基础上的操作

 双链表的尾删

//尾删
void LTPopBack(ListNode* phead)
{
	assert(phead);
	assert(!Empty(phead));

	ListNode* del = phead->prev;
	ListNode* prev = del->prev;

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

	free(del);
	del = NULL;
}

 这边仍然是在尾插的基础上的操作

这边我们进行了五次尾删,所以代码assert断言了!

将一次尾删注释,下面就是尾删的效果 

双链表的头删 

//头删
void LTPopFront(ListNode* phead)
{
	assert(phead);
	assert(!Empty(phead));
	ListNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

这个仍然是在尾插的基础上操作的,如果继续删除,跟上面的情况一样assert断言报错 

以上就是最基础的增删 ,下面开始加大难度!

双链表中查找数据pos

//找相同数据
ListNode* LTFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

这边我们查找的是数据3,所以我们可以找到 

 

这个在链表中没有数据6,所以我们没有找到相关的数据 

 在pos之后插入结点

这个和尾插以及头插其实是类似的,这里主要是寻找到pos结点,然后插入想要的数据

//在pos之后插入结点
void LTInsert(ListNode* pos, LTDataType x)
{
    assert(pos);

    ListNode* newnode = LTBuyNode(x);

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

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

在3的后面插入数据10 

 

删除pos结点 

​//删除指定位置节点
void LTErase(ListNode* pos)
{
	assert(pos);
	// pos->prev  pos   pos->next

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

	free(pos);
	pos = NULL;
}

​

双链表的销毁 

这里我们提供了两种的销毁方法,两种方法基本是类似的

//销毁
void LTDesTroy(ListNode** pphead)
{
	assert(pphead && *pphead);
	ListNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//销毁哨兵位结点
	free(*pphead);
	*pphead = NULL;
	pcur = NULL;
}
//为了更好的记忆,我们让销毁也传递一级指针
void LTDesTroy2(ListNode* phead)//传一级,需要手动将plist置为NULL
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = pcur = NULL;
}

最后我们将双向链表的源码附上

list.h

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

typedef int LTDataType;
typedef struct ListNode ListNode;
struct ListNode
{
	LTDataType data;
	ListNode* next;
	ListNode* prev;
};

ListNode* LTBuyNode(LTDataType x);
//为了保存接口的一致性
//
//初始化
//void LTInit(ListNode** pphead);
ListNode* LTInit();  

//
void LTPrint(ListNode* phead);

bool Empty(ListNode* phead);

//插入
//第一个参数传一级还是二级,要看phead指向的结点会不会改变
//如果发生改变,那么pphead的改变要影响实参,传二级
//如果不发生改变,pphead不会影响实参,传一级

//尾插
void LTPushBack(ListNode* phead, LTDataType x);

//头插
void LTPushFront(ListNode* phead, LTDataType x);

//尾删
void LTPopBack(ListNode* phead);

//头删
void LTPopFront(ListNode* phead);

//在pos之后插入结点
void LTInsert(ListNode* pos, LTDataType x);

//删除指定位置的结点
void LTErase(ListNode* pos);

//找数据
ListNode* LTFind(ListNode* phead, LTDataType x);

//销毁
void LTDesTroy(ListNode** pphead);
void LTDesTroy2(ListNode* phead);//传一级,需要手动将plist置为NULL

 List.c

#include"List.h"

ListNode* LTBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;

	return newnode;
}
//初始化

//void LTInit(ListNode** pphead)
//{
//	//创建一个头结点即“哨兵位”
//	*pphead = LTBuyNode(-1);
//}
ListNode* LTInit()
{
	ListNode* phead = LTBuyNode(-1);
	return phead;
}

//打印
void LTPrint(ListNode* phead)
{
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

bool Empty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

//尾插
void LTPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	//创建需要插入的结点
	ListNode* newnode = LTBuyNode(x);

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

	phead->prev->next = newnode;
	phead->prev = newnode;
}
//头插
void LTPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = LTBuyNode(x);

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

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

//尾删
void LTPopBack(ListNode* phead)
{
	assert(phead);
	assert(!Empty(phead));

	ListNode* del = phead->prev;
	ListNode* prev = del->prev;

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

	free(del);
	del = NULL;
}

//头删
void LTPopFront(ListNode* phead)
{
	assert(phead);
	assert(!Empty(phead));
	ListNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}
//找相同数据
ListNode* LTFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在pos之后插入结点
void LTInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = LTBuyNode(x);

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

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

//删除指定位置节点
void LTErase(ListNode* pos)
{
	assert(pos);
	// pos->prev  pos   pos->next

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

	free(pos);
	pos = NULL;
}

//销毁
void LTDesTroy(ListNode** pphead)
{
	assert(pphead && *pphead);
	ListNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//销毁哨兵位结点
	free(*pphead);
	*pphead = NULL;
	pcur = NULL;
}

void LTDesTroy2(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = pcur = NULL;
}

test.c 

#include"List.h"
void test()
{
	创建双向链表变量
	//ListNode* plist = NULL;
	//LTInit(&plist);
	ListNode* plist = LTInit();

	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);

	ListNode* pos = LTFind(plist, 3);
	LTInsert(pos, 10);
	LTPrint(plist);

	pos = LTFind(plist, 3);
	LTErase(pos);
	LTPrint(plist);

	/*LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);*/

	/*LTPushFront(plist, 1);
	LTPrint(plist);
	LTPushFront(plist, 2);
	LTPrint(plist);
	LTPushFront(plist, 3);
	LTPrint(plist);
	LTPushFront(plist, 4);
	LTPrint(plist);*/

	/*LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);*/

	/*if (pos == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}*/

	LTDesTroy(&plist);
	//LTDesTroy2(plist);
	//plist = NULL;


	
}

int main()
{
	test();
	return 0;
}

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

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

相关文章

学习008-02-04-09 Assign a Standard Image(分配标准图像)

Assign a Standard Image&#xff08;分配标准图像&#xff09; This lesson explains how to associate an entity class with a standard image from the DevExpress.Images assembly. This image illustrates the entity class in the following sections of the UI: 本课介…

C# Unity 面向对象补全计划 之 访问修饰符

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列旨在通过补全学习之后&#xff0c;给出任意类图都能实现并做到逻辑上严丝合缝

LabVIEW安装DSC模块 转自三景页593

打开NI Package Manager&#xff0c;找到LabVIEW and Drivers 找到自己需要的版本进行下载 搜索需要的模块进行下载 以DSC模块为例&#xff0c;下载右边的安装即可 最后用激活工具激活即可使用

【AI大模型】:结合wxauto实现智能微信聊天机器人

文章目录 &#x1f9d0;一、wxauto简介&#x1f3af;二、wxauto的主要功能&#x1f4e6;三、wxauto的安装与使用1. wxauto的安装2. wxauto的简单使用3. wxauto的消息对象 &#x1f4bb;四、wxauto结合大模型实现简单的聊天机器人三、完整代码 &#x1f9d0;一、wxauto简介 wxa…

(day28)leecode——有效括号

描述 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))","(()())","(())()","()(())","…

视频转场SDK,高效集成,提升视频制作效率

传统的视频制作方式往往受限于单一的转场效果&#xff0c;难以在瞬息万变的市场中脱颖而出。美摄科技&#xff0c;作为视频处理技术的领航者&#xff0c;以其革命性的视频转场SDK&#xff0c;为企业视频创作带来了前所未有的创意与效率飞跃。 重塑转场艺术&#xff0c;激发创意…

el-table 表格列宽自适应

思路&#xff1a;获取当前列的最长数据和表头名称比较&#xff0c;取大值赋值给宽度。 效果图 自适应前 自适应后 自适应方法 // col 里面包含了表头字段和名称&#xff0c;list 是表格数据 columnWidth(col, list) {let prop col.prop, lab col.lab;let width 90; // 设…

数据结构——双向链表及其总结

1.概述 链表根据是否带头、是否双向、是否循环可以分为八种&#xff0c;双向链表是典型的带头双向循环链表。 双向链表的结构可以表示为下&#xff1a; struct ListNode {int data;struct ListNode* next;struct ListNode* prev; } 2.双向链表的实现过程及其解析 双向链表…

Redis 内存淘汰策略

Redis 作为一个内存数据库&#xff0c;必须在内存使用达到配置的上限时采取策略来处理新数据的写入需求。Redis 提供了多种内存淘汰策略&#xff08;Eviction Policies&#xff09;&#xff0c;以决定在内存达到上限时应该移除哪些数据。

如何理解Java的内存模型

希望文章能给到你启发和灵感~ 如果觉得文章对你有帮助的话,点赞 + 关注+ 收藏 支持一下博主吧~ 阅读指南 开篇说明一、内存相关基础了解1.1 硬件的内存架构1.2 缓存一致性问题1.3 内存模型的出现二、Java内存模型2.1 组成部分2.2 模型特性2.4 As-if-serial语义与Happens-bef…

【2024蓝桥杯/C++/B组/前缀总分】

题目 代码 #include<bits/stdc.h> using namespace std;// 定义常量N为210&#xff0c;用于数组的大小 const int N 210; // 声明n为整型变量&#xff0c;用于存储字符串的数量 int n; // 声明一个字符串数组str&#xff0c;大小为N&#xff0c;用于存储字符串 string …

pda条码扫描手持机,数据采集器助力景区售检票

随着科技的发展&#xff0c;景区也在往信息化方向发展&#xff0c;景区为了提升售票、检票入园效率&#xff0c;可采用pda条码扫描手持机售检票系统。pda条码扫描手持机是一种便携式的检票设备&#xff0c;可以随时随地使用。配备5.5寸高清大屏,1440*720分辩率&#xff0c;多点…

安防监控视频融合汇聚平台EasyCVR创建新用户时没有摄像机权限,是什么原因?

国标GB28181/RTSP/ONVIF视频管理系统EasyCVR视频汇聚平台&#xff0c;是一个具备高度集成化、智能化的多协议接入视频监控汇聚管理平台&#xff0c;拥有远程视频监控、录像、云存储、录像检索与回放、语音对讲、云台控制、告警、平台级联等多项核心功能。EasyCVR安防监控视频系…

docker笔记5-数据卷

docker笔记5-数据卷 一、数据卷1.1 定义1.2 本质1.3 特点 二、使用数据卷三、案例2.1 安装Mysql 四、匿名挂载和具名挂载4.1 匿名挂载4.2 具名挂载 五、三种挂载方式 一、数据卷 1.1 定义 Docker 容器数据卷是一个可用于存储数据的特殊目录&#xff0c;存在于一个或多个容器的…

免费【2024】springboot 出租车管理网站的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

unity2D游戏开发15自我防御

创建子弹 Ammo类来表示自子弹 创建一个GameObject,并命名为AmmoObject 将Ammo.png拖入Object中 属性设置,点击apply 将SpriteRenderer组件添加到AmmoObject,将Sorting Layer设置为Characters,并将Sprite属性设置为Ammo 将CircleCollider2D添加到AmmoObject。确保选中Is Tr…

【C++高阶】哈希的应用(封装unordered_map和unordered_set)

✨ 世事漫随流水&#xff0c;算来一生浮梦 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&…

KubeSphere部署:(三)MySQL安装

MySQL没有什么特殊的&#xff0c;这里记录一下部署过程(本文示例中安装的版本为5.7.29)。步骤大致如下&#xff1a; 拉取docker镜像 -> 标记并推送至私有harbor -> 创建有状态负载 -> 创建服务 一、拉取镜像&#xff0c;并推送至私有harbor # 拉取镜像 docker pull …

PP氮气柜的特点和使用事项介绍

PP材质&#xff0c;全称为聚丙烯&#xff0c;是一种热塑性塑料&#xff0c;具有质轻、强度高、耐化学腐蚀性好、无毒无味、耐热性佳等优点。它在众多塑料材料中脱颖而出&#xff0c;特别是在需要耐腐蚀和长期使用的应用中&#xff0c;表现尤为出色。 PP材质具有优秀的化学稳定性…

Loadrunner12 回放脚本查看接口响应数据

1、如下图所示&#xff0c;回放脚本后&#xff0c;点击快照-http数据-点击需要查看的接口-点击Json视图&#xff0c;最后点击响应正文&#xff0c;即可查看接口的响应数据