【数据结构】5. 双向链表

news2025/6/7 15:41:59

文章目录

  • 一、链表的分类
    • 1、双向链表的结构
  • 二、双向链表的实现
    • 0、准备工作
    • 1、初始化
    • 2、打印
    • 3、尾插
    • 4、头插
    • 5、尾删
    • 6、头删
    • 7、查找
    • 8、在指定位置之后插入数据
    • 9、删除指定位置
    • 10、销毁

一、链表的分类

链表总共分为8种,具体的分组方式如图所示:
在这里插入图片描述

  • 带头指的是链表中的哨兵位,这个哨兵位也就是头结点,哨兵位存在的意义是 遍历循环链表避免死循环

  • 单向和双向的区别如图:
    在这里插入图片描述

  • 循环主要是看尾结点的next指针是否指向NULL。如图:

我们前面所学的单链表其实就是:不带头单向不循环链表

1、双向链表的结构

双向链表的结构如图所示:
在这里插入图片描述
可以看到双向链表其实是:带头双向循环链表

二、双向链表的实现

0、准备工作

实现双向链表前需要我们创建三个文件。
List.h:节点的定义,方法的声明。
List.c:方法的实现。
test.c:方法的测试。

接着再在List.h文件中定义我们双向链表节点的结构体,以及方法的声明和包含的头文件。

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

//双向链表节点的结构体
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;//后继指针
	struct ListNode* prev;//前驱指针
}LTNode;


//双向链表的初始化
void LTInit(LTNode** pphead);

//双向链表的销毁
void LTDesTroy(LTNode* phead);

//双向链表的打印
void LTPrint(LTNode* phead);

//不能改变哨兵位的地址,因此传一级指针
//尾插
void LTPushBack(LTNode* phead, LTDataType x);

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

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

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

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除指定位置
void LTErase(LTNode* pos);

接下来就在List.c文件中来实现具体的方法。
实现方法前需要包含头文件#include"List.h"

1、初始化

双向链表的初始化其实就是给双向链表创建一个哨兵位。

因此我们先要实现一个申请节点的函数:

申请节点函数:

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc failed");
		return 1;
	}

	node->data = x;
	node->next = node->prev = node;
	return node;
}

接下来再来实现双向链表的初始化:

初始化函数:

void LTInit(LTNode** pphead)
{
	*pphead = LTBuyNode(-1);
}

我们在test.c文件中测试方法前,要先包含头文件#include"List.h"

现在开始测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);
}

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

我们打开调试,观察监视窗口:
在这里插入图片描述
发现已经初始化成功!

2、打印

为了方便我们观察方法的正确性,我们可以创建一个函数用来打印链表的内容(除哨兵位的所有节点)。

打印函数:

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

3、尾插

在这里插入图片描述
尾插函数:

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

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

}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

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

运行结果:
在这里插入图片描述

4、头插

头插函数:

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

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

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);
}

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

运行结果:
在这里插入图片描述

5、尾删

void LTPopBack(LTNode* phead)
{
	LTNode* del = phead->prev;
	
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

	LTPopBack(plist);
	LTPrint(plist);
}

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

运行结果:
在这里插入图片描述

6、头删

头删函数:

void LTPopFront(LTNode* phead)
{
	LTNode* del = phead->next;

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

	free(del);
	del = NULL;
}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

	LTPopFront(plist);
	LTPrint(plist);
}

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

运行结果:
在这里插入图片描述

7、查找

查找思路:遍历除了哨兵位的所有节点,如果节点对应的数据等于要查找的数据,那就返回节点的地址,否则返回 NULL。

查找函数:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;

	while (pcur != phead)
	{
		if (x == pcur->data)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

	int find = LTFind(plist, 2);
	if (find == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}
	
}

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

运行结果:
在这里插入图片描述

8、在指定位置之后插入数据

注:指定位置指的是节点对应的地址,一般需要搭配查找函数一起使用。

在指定位置后插入函数:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

	int find = LTFind(plist, 2);
	LTInsert(find, 10);
	LTPrint(plist);
}

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

运行结果:
在这里插入图片描述

9、删除指定位置

注:指定位置也指的是节点对应的地址,要搭配查找函数一起使用。

删除指定位置函数:

void LTErase(LTNode* pos)
{
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;
}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

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

	int find = LTFind(plist, 2);
	LTErase(find);
	LTPrint(plist);
}

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

10、销毁

思路:要先记录后一个节点,再销毁当前的节点。

销毁函数:

void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}

	free(phead);
	phead = NULL;
}

再进行测试:

void test()
{
	LTNode* plist = NULL;
	LTInit(&plist);

	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	printf("销毁前:");
	LTPrint(plist);

	LTDesTroy(plist);
	printf("销毁后:");
	LTPrint(plist);
}

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

运行结果:
在这里插入图片描述

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

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

相关文章

【Linux手册】冯诺依曼体系结构

目录 前言 五大组件 数据信号 存储器&#xff08;内存&#xff09;有必要吗 常见面试题 前言 冯诺依曼体系结构是当代计算机基本架构&#xff0c;冯诺依曼体系有五大组件&#xff0c;通过这五大组件直观的描述了计算机的工作原理&#xff1b;学习冯诺依曼体系可以让给我们更…

Mobile App UI自动化locator

在开展mobile app UI层自动化测试时&#xff0c;编写目标元素的locator是比较耗时的一个环节&#xff0c;弄清楚locator背后的逻辑&#xff0c;可以有效降低UI层测试维护成本。此篇博客以webdriverioappium作为UI自动化工具为例子&#xff0c;看看有哪些selector方法&#xff0…

(LeetCode 每日一题) 1061. 按字典序排列最小的等效字符串 (并查集)

题目&#xff1a;1061. 按字典序排列最小的等效字符串 思路&#xff1a;使用并查集&#xff0c;来将等价的字符连起来&#xff0c;形成一棵树。这棵树最小的字母&#xff0c;就代表整颗树&#xff0c;时间复杂度0(n)&#xff0c;细节看注释。 C版本&#xff1a; class Solutio…

linux 安装mysql8.0;支持国产麒麟,统信uos系统

一&#xff1a;使用我已经改好的mysql linux mysql8.0解压可用&#xff0c;点我下载 也在国产麒麟系统&#xff0c;统信uos系统也测试过&#xff0c;可用&#xff1b; 下载后&#xff0c;上传mysql.tar.gz 然后使用root角色去执行几个命令即可&#xff1b;数据库密码&#xf…

C#实现远程锁屏

前言 这是一次提前下班没有锁屏进而引发的一次思考后的产物&#xff0c;思考的主要场景是当人离开电脑后&#xff0c;怎么能控制电脑锁屏&#xff0c;避免屏幕上的聊天记录被曝光。 首先想到通过系统的电源计划设置闲置超时时间熄屏&#xff0c;这可能是最接近场景的解决方案&a…

SpringBoot3整合MySQL8的注意事项

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 注意事项 1、请添加添加如下依赖&#xff1a; <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><…

智语心桥:当AI遇上“星星的孩子”,科技如何点亮沟通之路?

目录: 引言:当科技的温度,遇见“星星的孩子”“智语心桥”:一座为孤独症儿童搭建的AI沟通之桥核心技术探秘:AI如何赋能“读心”与“对话”?个性化魔法:AI如何实现“千人千面”的精准干预?应用场景畅想:从家庭到机构,AI的全方位支持为什么是“智语心桥”?——价值、可…

itop-3568开发板机器视觉opencv开发手册-图像绘制-画线

本小节代码在配套资料“iTOP-3568 开发板\03_【iTOP-RK3568 开发板】指南教程 \04_OpenCV 开发配套资料\11”目录下&#xff0c;如下图所示&#xff1a; cv2.line 函数功能&#xff1a; 绘制一条直线。 函数原型&#xff1a; cv2.line(img,pt1,pt2,color,thicknessNone,lin…

sudo docker exec -it backend bash 以交互方式(interactive)进入正在运行的 Docker 容器的命令行环境

sudo docker exec -it backend bash&#x1f50d; 总体作用 这条命令的作用是&#xff1a; 以交互方式&#xff08;interactive&#xff09;进入名为 backend 的正在运行的 Docker 容器的命令行环境。 你会进入容器的“终端”&#xff0c;就像登录到一个 Linux 系统一样&#…

浅析EXCEL自动连接PowerBI的模板

浅析EXCEL自动连接PowerBI的模板 之前我分享过&#xff1a;PowerBI链接EXCEL实现自动化报表 &#xff0c;其中一个关键工具就是提到的EXCEL链接模板&#xff0c;即宏工作薄。 今天就大概来聊一聊这个宏工作簿的底层原理是啥&#xff0c;怎么实现的。 第一步&#xff1a; 打开…

java32

1.反射 获取类&#xff1a; 获取构造方法&#xff1a; 获取权限修饰符&#xff1a; 获取参数信息&#xff1a; 利用反射出来的构造器来创建对象&#xff1a; 获取成员变量&#xff1a; 获取成员方法&#xff1a; 综合练习&#xff1a; 动态代理&#xff1a;

【Redis】zset 类型

zset 一. zset 类型介绍二. zset 命令zaddzcard、zcountzrange、zrevrange、zrangebyscorezpopmax、zpopminzrank、zrevrank、zscorezrem、zremrangebyrank、zremrangebyscorezincrby阻塞版本命令&#xff1a;bzpopmax、bzpopmin集合间操作&#xff1a;zinterstore、zunionstor…

从Gartner报告看Atlassian在生成式AI领域的创新路径与实践价值

本文来源atlassian.com&#xff0c;由Atlassian全球白金合作伙伴——龙智翻译整理。 二十余年来&#xff0c;Atlassian始终是创新领域的领军者。凭借对团队协作本质的深刻理解&#xff0c;Atlassian在AI时代仍持续引领协作方式的革新。如今&#xff0c;这一领先地位再次获得权威…

Kafka 安装教程(支持 Windows / Linux / macOS)

一、下载 1、kafka官网下载地址:https://kafka.apache.org/downloads 根据实际情况下载对应的版本 2、JDK的版本最好是17+ JDK下载地址:https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html 二、安装 前置条件 安装 Java(至少 Jav…

OpenCV种的cv::Mat与Qt种的QImage类型相互转换

一、首先了解cv::Mat结构体 cv::Mat::step与QImage转换有着较大的关系。 step的几个类别区分: step:矩阵第一行元素的字节数step[0]:矩阵第一行元素的字节数step[1]:矩阵中一个元素的字节数step1(0):矩阵中一行有几个通道数step1(1):一个元素有几个通道数(channel()) cv::Ma…

前端没有“秦始皇“,但可以做跨端的王[特殊字符]

前端各领域的 “百家争鸣” 框架之争&#xff1a;有 React、Vue、Angular 等多种框架。它们各有优缺点&#xff0c;开发者之间还存在鄙视链&#xff0c;比如 Vue 嫌 React 难用&#xff0c;React 嫌 Vue 不够灵活。样式处理&#xff1a; CSS 预处理器&#xff1a;像 Sass、Les…

mongodb源码分析session异步接受asyncSourceMessage()客户端流变Message对象

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制&#xff0c;ASIOSession和connection是循环接受客户端命令&#xff0c;状态转变流程是&#xff1a;State::Created 》 State::Source 》State::…

【数据分析】什么是鲁棒性?

引言 —— 为什么我们需要“抗折腾”的系统&#xff1f; 当你乘坐的飞机穿越雷暴区时机体剧烈颠簸&#xff0c;自动驾驶汽车在暴雨中稳稳避开障碍物&#xff0c;或是手机从口袋摔落后依然流畅运行——这些场景背后&#xff0c;都藏着一个工程领域的“隐形守护者”&#xff1a;…

Qt/C++学习系列之QGroupBox控件的简单使用

Qt/C学习系列之QGroupBox控件的简单使用 前言样式使用代码层面初始化控件事件过滤器点击事件处理 总结 前言 最近在练手一个项目&#xff0c;项目中有不同功能的划分&#xff0c;为了功能分区一目了然&#xff0c;我使用到QGroupBox控件&#xff0c;也是在界面排版布局中最常用…

如何轻松地将数据从 iPhone传输到iPhone 16

对升级到 iPhone 16 感到兴奋吗&#xff1f;恭喜&#xff01;然而&#xff0c;除了兴奋之外&#xff0c;学习如何将数据从 iPhone 传输到 iPhone 16 也很重要。毕竟&#xff0c;那些重要的联系人、笔记等都是不可或缺的。为了实现轻松的iPhone 到 iPhone 传输&#xff0c;我们总…