数据结构之链表(双链表)

news2025/3/25 13:36:04

目录

一、双向带头循环链表

概念

 二、哨兵位的头节点

优点:

头节点的初始化

三、带头双向链表的实现

1.双链表的销毁

2.双链表的打印

3.双链表的尾插和头插

尾插:

头插:

4.双链表的尾删和头删

尾删:

头删:

5.双链表的查找

四、测试代码


一、双向带头循环链表

概念

        名如其实,这个链表结构有哨兵位头节点,双向并且循环,结构最复杂。一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了这里我们用一张图片就能很好的看清楚双向带头循环链表的结构了。

 二、哨兵位的头节点

        从上一篇文章我们就在说带头/不带头,那么这个头是什么呢?其实它就是哨兵位的头节点。这个节点一般在一个链表的最前方的位置,不用来储存数据。

优点:

1.    简化边界条件处理:
•    在没有哨兵节点的情况下,链表的头插、头删等操作需要特别处理头节点为空的情况。
•    使用哨兵节点后,头节点始终存在,简化了插入和删除操作的逻辑,不需要单独处理头节点为空的情况。

2.    统一操作逻辑:
•    无论是头插、尾插、头删还是尾删,操作逻辑都可以统一处理,不需要区分是否是第一个节点。
•    例如,插入操作总是插入到哨兵节点之后,删除操作总是删除哨兵节点之后的节点。

3.    提高代码可读性和维护性:
•    由于边界条件处理简化,代码逻辑更加清晰,减少了出错的可能性。
•    代码维护起来也更加方便,因为不需要在多个地方处理特殊情况。
4.    便于实现某些算法:
•    在某些算法中,使用哨兵节点可以避免多次检查链表是否为空的情况,提高算法的效率。

头节点的初始化

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = 0;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

三、带头双向链表的实现

1.双链表的销毁

        与单链表的销毁类似,需要定义一个指针来遍历整个链表,但是注意,如果是从头节点开始遍历,我们会因为无法很好的控制停止条件而导致无限循环,所以我们从头节点的下一个开始遍历,当这个cur指针指向头节点时就停止,这里后面还会反复用到,请务必想清楚。

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

2.双链表的打印

// 双向链表打印
void ListPrint(ListNode* plist)
{
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

3.双链表的尾插和头插

尾插:

// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	// 双向链表的找尾很简单,只需要指向plist的前一个节点就行
	ListNode* newnode = buyNewnode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}

头插:

// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* newnode = buyNewnode(x);
	ListNode* head = plist->next;
	plist->next = newnode;
	newnode->prev = plist;
	newnode->next = head;
	head->prev = newnode;
}

4.双链表的尾删和头删

尾删:

// 双向链表尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	if (plist->next == plist)
	{
		return;
	}
	ListNode* tail = plist->prev;
	ListNode* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;
}

头删:

// 双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	if (plist->next = plist)
	{
		return;
	}
	ListNode* head = plist->next;
	ListNode* newHead = head->next;
	free(head);
	head = NULL;
	plist->next = newHead;
	newHead->prev = plist;
}

5.双链表的查找

// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

四、测试代码


// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next; 
	struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* buyNewnode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
ListNode* ListCreate()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = 0;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}
// 双向链表销毁
void ListDestory(ListNode* plist)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
	plist = NULL;
}
// 双向链表打印
void ListPrint(ListNode* plist)
{
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	// 双向链表的找尾很简单,只需要指向plist的前一个节点就行
	ListNode* newnode = buyNewnode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	if (plist->next == plist)
	{
		return;
	}
	ListNode* tail = plist->prev;
	ListNode* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* newnode = buyNewnode(x);
	ListNode* head = plist->next;
	plist->next = newnode;
	newnode->prev = plist;
	newnode->next = head;
	head->prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	if (plist->next = plist)
	{
		return;
	}
	ListNode* head = plist->next;
	ListNode* newHead = head->next;
	free(head);
	head = NULL;
	plist->next = newHead;
	newHead->prev = plist;
}
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
int main()
{
	ListNode* plist = ListCreate();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListPushFront(plist, 0);
	ListPrint(plist);

	ListPopFront(plist);
	ListPrint(plist);

	return 0;
}

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

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

相关文章

uniapp从 vue2 项目迁移到 vue3流程

以下是必须为迁移到 vue3 进行调整的要点&#xff0c;以便 vue2 项目可以在 vue3 上正常运行。 1. 在index.js中创建应用程序实例 // Before - Vue 2 import Vue from vue import App from ./App // with no need for vue3 Vue.config.productionTip false // vue3 is no lon…

案例:网络命名空间模拟隔离主机场景

场景描述 假设我们需要在同一台物理机上模拟两台独立的主机&#xff08;Host A 和 Host B&#xff09;&#xff0c;它们分别位于不同的网络命名空间中&#xff0c;并通过虚拟以太网对&#xff08;veth pair&#xff09;进行通信。目标是展示网络命名空间的隔离性和跨命名空间的…

23种设计模式-生成器(Builder)设计模式

工厂方法设计模式 &#x1f6a9;什么是生成器设计模式&#xff1f;&#x1f6a9;生成器设计模式的特点&#x1f6a9;生成器设计模式的结构&#x1f6a9;生成器设计模式的优缺点&#x1f6a9;生成器设计模式的Java实现&#x1f6a9;代码总结&#x1f6a9;总结 &#x1f6a9;什么…

蓝桥杯备考:BFS最短路径之Meteor Shower S流星雨

本题是一个BFS最短路问题&#xff0c;我们可以先把时刻的矩阵搞出来&#xff0c;哪些时刻哪些方块儿不能走用来剪枝 如果第一次走到永远不会被扎到的区域&#xff0c;那时候就是我们的最短距离 定义方向向量 #include <iostream> #include <queue> #include <c…

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的 RESTful API 设计:从上手到骨折

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、开篇整活…

数据结构5(初):排序

目录 1、排序的概念以及常见的排序算法 1.1、排序的概念 1.2、常见的排序算法 2、常见排序算法的实现 2.1、插入排序 2.1.1、直接插入排序 2.1.2、希尔排序 2.2、选择排序 2.2.1、直接选择排序 2.2.2、堆排序 2.3、交换排序 2.3.1、冒泡排序 2.3.2、快速排序 2.3.…

2025-03-23 学习记录--C/C++-C语言 sprintf()实现将多个值按指定格式拼接成字符串

C语言 sprintf()实现将多个值按指定格式拼接成字符串 举个例子 &#x1f330;&#xff1a;将字符串 “m” 与数字 0、1、2 动态拼接成 “m0”、“m1”、“m2”&#xff1a;&#x1f447;&#x1f3fb; #include <stdio.h> // 包含标准输入输出库&#xff0c;用于使用输入…

【小程序开发】完整项目结构长啥样?

Hello,欢迎来到AI技术库。AI写代码的时代,人人都可以成为程序员。欢迎继续【小程序开发】系列课。上节课中,我们学习了【手把手教你小程序开发】什么是大前端?,本节课,我们学习第二篇 小程序的完整项目结构。 本文适合阅读对象: 1. 非计算机专业AI爱好者;2. 小程序开发…

计算机网络精讲day2———计算机网络的性能指标(下)

性能指标5&#xff1a;时延带宽积 时延带宽积传播时延*带宽 这里要注意是传播时延不是发送时延 重点&#xff1a;管道法解析时延带宽积 我们以一个圆柱形管道来代表链路&#xff0c;管道的长度是链路的传播时延&#xff08;以时间作为单位单位表示链路长度&#xff09;&#x…

【多线程】初始线程和Thread类

一. 线程 1. 线程的引入 虽然进程已经可以解决并发编程这种问题&#xff0c;但是进程在频繁进行创建和销毁的时候&#xff0c;系统开销非常大&#xff0c;如果一个服务器向你发送多个请求&#xff0c;针对每一个请求&#xff0c;都需要创建一个进程来应答&#xff0c;每个进程…

WebLogic中间件常见漏洞

一、后台弱⼝令GetShell 1.环境搭建 cd vulhub-master/weblogic/weak_password docker-compose up -d 2.访问网站并登陆后台 /console/login/LoginForm.jsp 默认账号密码&#xff1a;weblogic/Oracle123 3.点击部署&#xff0c;点击安装&#xff…

[笔记.AI]多头自注意力机制(Multi-Head Attention)

多头自注意力是深度学习领域&#xff0c;特别是自然语言处理&#xff08;NLP&#xff09;和Transformer模型中的关键概念。其发展源于对序列数据中复杂依赖关系的建模需求&#xff0c;特别是在Transformer架构的背景下。 举例 比喻-读长篇文章 用一个简单的比喻来理解“多头注…

【基于ROS的A*算法实现路径规划】A* | ROS | 路径规划 | Python

### 记录一下使用Python实现ROS平台A*算法路径规划 ### 代码可自取 &#xff1a;Xz/little_projecthttps://gitee.com/Xz_zh/little_project.git 目录 一、思路分析 二、算法实现 三、路径规划实现 一、思路分析 要求使用A*算法实现路径规划&#xff0c;可以将该任务分为三…

keda基于postgresql伸缩dify-api服务

1 概述 dify-api使用postgresql来存储数据&#xff0c;在dify控制台每新建一个聊天机器的聊天框&#xff0c;就会在conversations表里新插入一条记录&#xff0c;并且不断地更新字段updated_at&#xff0c;示例如下&#xff1a; dify# select * from conversations limit 1; …

趣味极简品牌海报艺术贴纸设计圆润边缘无衬线粗体装饰字体 Chunko Bold - Sans Serif Font

Chunko Bold 是一种功能强大的显示字体&#xff0c;体现了大胆极简主义的原则 – 当代设计的主流趋势。这种自信的字体将粗犷的几何形状与现代的趣味性相结合&#xff0c;具有圆润的边缘和强烈的存在感&#xff0c;与当今的极简主义设计方法完美契合。无论是用于鲜明的构图还是…

指针,数组 易混题解析(一)

目录 一.相关知识点 1.数组名是什么&#xff1f; 两个例外&#xff1a; 2.strlen 3.sizeof 4. * ( ) 与 [ ] 的互换 二.一维数组 三.字符数组 1. 字符 &#xff08;1&#xff09;sizeof &#xff08;2&#xff09;strlen 2.字符串 &#xff08;1&#xff09;si…

2025前端面试题记录

vue项目目录的执行顺序是怎么样的&#xff1f; 1、package.json   在执行npm run dev时&#xff0c;会在当前目录寻找package.json文件&#xff0c;此文件包含了项目的名称版本、项目依赖等相关信息。 2、webpack.config.js(会被vue-cli脚手架隐藏) 3、vue.config.js   对…

复变函数摘记2

复变函数摘记2 3. 级数3.1 复数项级数3.2 复变幂级数3.3 泰勒级数3.4 洛朗级数 3. 级数 \quad 复数项级数的一般项 α n a n i b n \alpha_na_n\text{i}b_n αn​an​ibn​ 为复数&#xff0c;与高等数学中无穷级数的分析方式类似&#xff0c;也是通过和函数来研究级数的收敛…

高频面试题(含笔试高频算法整理)基本总结回顾67

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

Kafka--常见问题

1.为什么要使用 Kafka&#xff0c;起到什么作用 Kafka是一个高吞吐量、分布式、基于发布订阅的消息系统&#xff0c;它主要用于处理实时数据流 Kafka 设计上支持高吞吐量的消息传输&#xff0c;每秒可以处理数百万条消息。它能够在处理大量并发请求时&#xff0c;保持低延迟和…