【C语言】双向链表详解

news2024/11/25 10:55:51

文章目录

  • 关于双向链表
  • 双向链表的初始化
  • 双向链表的打印
  • 双向链表方法调用 - 尾删为例
  • 双向链表的查找 - 指定位置之后插入为例
  • 双向链表结束 - 链表的销毁
  • 小结及整体代码实现


关于双向链表

首先链表有8种基本分法 在这里插入图片描述

其中在笔者之前文章种详细介绍的 单链表不带头单项不循环链表
而今天笔者要介绍的就是 带头双向循环链表 双向链表

我们可以把链表理解为下图

在这里插入图片描述
所以我们双向链表的结构往往是这样的
在这里插入图片描述

typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{
	int data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

双向链表的初始化

第一步我们需要进行双向链表的初始化,双向链表的初始化需要注意的一个问题是,传进去的参数是一级指针还是二级指针,这本应该后面大致写完了再讨论,但是这里提前说了

在笔者后面的方法中,例如尾插,头插,尾删,头删等都是用一级指针,这是因为双向链表有一个好处,我们存在一个哨兵位,这个节点是初始化时创立的,不需要改变,同时有存储着前后节点的地址,只需要通过成员名调用即可,这里呈现一下会出现的方法参数

//打印
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);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

笔者这里据大多数关于双向链表的地址传参都是一级指针,这在一定程度上,保证了接口的一致性,为后来调用双向链表数据结构者降低了理解成本,这也就是我们在设计程序时需要注意的一点

笔者这里通过二级指针传参和无传参的方法各创建了一个初始化方法
大家可以看到两种方法连语句都基本一致,但是后者明显会比前者好,因为在以后不论是我们还是其他人通过一览所有的方法名后,不需要刻意去记初始化需要传参二级指针,直接空着使用就行了

//申请节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc newnode");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;//指向自己

	return newnode;
}

//初始化1
void LTInit(LTNode** pphead)
{
	//该双向链表创建一个哨兵位
	*pphead = LTBuyNode(-1);
}
//初始化2
LTNode* newLTInit()
{
	return LTBuyNode(-1);
}

双向链表的打印

双向链表的打印比单链表好理解太多了,
因为存在一个无意义的 哨兵位 用来记录开头,链接末尾,
所以只需要新创建一个新的节点用来帮忙定位当前位置,如果这个节点的值跟 哨兵位 一样,便退出

代码如下

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

双向链表方法调用 - 尾删为例

大家可以先看一下下图,我们在双向链表的末尾放上新节点

在这里插入图片描述

为了保证双向链表在使用时不发生意外,
我们先将 newnodeprevnext 对接好 d3 和 头节点phead
这样做只是给新的节点的前后向确定好,不影响原来的双向链表

再将 d3next 对准 newnode
pheadprev 对准 newnode

下面代码呈现

//尾插
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;
}

双向链表的查找 - 指定位置之后插入为例

在这里插入图片描述

指定位置之后插入,除了插入这个重要点,就是指定位置这个大头了,那么我们该如何实现找到指定位置呢

同样因为 哨兵位 的存在,我们新定义节点来一个一个对比下去,知道等于 哨兵位 为止,代表整个双向链表中都没有这个要查找的数据

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

既然知道如何找到指定位置,那么接下来就是插入了,
我们通过指定位置的节点信息,按照前面尾插的方法就行了,也是很简单的

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

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

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

双向链表结束 - 链表的销毁

因为我们前面有提到为保证接口的一致性我们同意设定为一级指针,但这样我们能做的就是将除 哨兵位 以外的节点全部置为空,但是外部所创建的节点,即我们一开始所初始化的点无法在内部置为空,所以出去后仍然需要置为空

//销毁
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
	pcur = NULL;//这点无所谓,跳出方法,就会为空
}

这是内部的销毁,出去后,在置为空一次,即

LTDestory(plist);
plist = NULL;

小结及整体代码实现

有一说一,双向链表真的很好理解,而且写方法时,往往不用循环就能很容易解出来,比单链表和数组方便许多,大家如果想要自己实现双向链表,尽量没完成一次方法时,调试检验一下,方便找到错误

下面为大家展现一下笔者的代码

“List.h”

#pragma once

#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)

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

typedef int LTDataType;
//定义双向链表节点的结构
typedef struct ListNode
{
	int data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;


//声明双向链表中提供的方法
//尽量都用一级指针,降低调用方的理解成本,保持接口的一致性
//初始化
//void LTInit(LTNode** pphead);
LTNode* newLTInit();

//打印
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);
//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

“List.c”

#include"List.h"


//申请节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc newnode");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;//指向自己

	return newnode;
}


//初始化
void LTInit(LTNode** pphead)
{
	//该双向链表创建一个哨兵位
	*pphead = LTBuyNode(-1);
}

LTNode* newLTInit()
{
	return LTBuyNode(-1);
}

//销毁
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
	pcur = NULL;//这点无所谓,跳出方法,就会为空
}


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


//尾插
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 LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

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

//尾删
void LTPopBack(LTNode* phead)
{
	//链表必须有效且不为空
	assert(phead && phead->next != phead);

	phead->prev->prev->next = phead;
	phead->prev = phead->prev->prev;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

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

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

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

	pos->next->prev = newnode;
	pos->next = newnode;
}
//在pos之前插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);
	
	newnode->next = pos;
	newnode->prev = pos->prev;

	pos->prev->next = newnode;
	pos->prev = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

测试文件就不展示了,没有什么参考价值,能够独自理解并写出上述方法名,一个简简单单的调试自然不在话下

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

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

相关文章

快速解锁3D Web渲染引擎HOOPS Communicator轻量化技术

在当今数字化时代&#xff0c;三维模型的使用已经成为许多行业中不可或缺的一部分。然而&#xff0c;随着模型复杂性的增加和数据量的膨胀&#xff0c;如何在Web浏览器中高效加载和渲染这些模型成为了一个挑战。慧都3D Web渲染引擎HOOPS Communicator通过其先进的轻量化技术&am…

测试过程和测试生命周期

软件测试过程是一系列有计划、有组织的活动&#xff0c;旨在识别和解决软件产品中的问题。这个过程通常包括多个阶段&#xff0c;每个阶段都有其特定的目标和方法。 需求分析&#xff1a; 分析软件需求和测试需求&#xff0c;确定测试的目标和范围。理解用户需求和业务目标&…

MM-Grounding-DINO的训练推理(待更新)

1、简单介绍 继前面发布的 GroundingDino 和 Open-GroundingDino的推理 和 Open-GroundingDino的训练实现&#xff0c;作为 GroundingDino延续性的文本检测网络 MM-Grounding-DINO 也发布了较详细的 训练和推理实现教程&#xff0c;而且操作性很强。作为学习内容&#xff0c;也…

我对硬技能与软技能的认知

今天看到一个很有意思的一段话&#xff0c;假设一个人的技能有两种&#xff0c;分别是&#xff1a;硬技能和软技能。 硬技能通常指的是与工作直接相关的、可以通过教育和培训获得的技能&#xff0c;如编程语言、会计知识等,这些技能往往有明确的衡量标准&#xff0c;容易通过考…

java 将 json 数据转为 java 中的对象

一、准备 json 数据 {"name": "mike","age": 17,"gender": 1,"subject": ["math","english"] }二、对应的java对象 package com.demo.controller;import lombok.Data; import java.util.List;Data pu…

鸿蒙TypeScript学习第13天:【元组】

1、TypeScript 元组 我们知道数组中元素的数据类型都一般是相同的&#xff08;any[] 类型的数组可以不同&#xff09;&#xff0c;如果存储的元素数据类型不同&#xff0c;则需要使用元组。参考文档&#xff1a;qr23.cn/AKFP8k 元组中允许存储不同类型的元素&#xff0c;元组…

【MATLAB第104期】基于MATLAB的xgboost的敏感性分析/特征值排序计算(针对多输入单输出回归预测模型)

【MATLAB第104期】基于MATLAB的xgboost的敏感性分析/特征值排序计算&#xff08;针对多输入单输出回归预测模型&#xff09; 因matlab的xgboost训练模型不含敏感性分析算法&#xff0c;本文通过使用single算法&#xff0c;即单特征因素对输出影响进行分析&#xff0c;得出不同…

Python 进度显示工具(tqdm)

tqdm 是一个进度显示工具&#xff0c;当任务执行的等待时间较长时&#xff0c;通过tqdm模块可以模拟出一个进度条&#xff0c;由此可以看到任务执行进度&#xff0c;获得更好的体验。 文章目录 一、tqdm的安装二、tqdm的使用2.1 基于可迭代对象2.2 手动进度更新2.3 命令行模式 …

【SpringBoot】mybatis-plus实现增删改查

mapper继承BaseMapper service 继承ServiceImpl 使用方法新增 save,updateById新增和修改方法返回boolean值,或者使用saveOrUpdate方法有id执行修改操作,没有id 执行新增操作 案例 Service public class UserService extends ServiceImpl<UserMapper,User> {// Au…

mac配置Jmeter环境

mac配置Jmeter环境 一、安装jmeter二、Jmeter目录结构三、汉化Jmeter四、jmeter安装第三方插件 一、安装jmeter 第一步先自行配置好电脑的jdk环境 1、官网下载jar包 https://jmeter.apache.org/download_jmeter.cgi 2、解压到软件安装目录 3、启动Jmeter 启动方式1️⃣&#x…

CSS特效---纯CSS实现点击切换按钮

1、演示 2、一切尽在代码中 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"w…

idea项目编译时报错:GC overhead limit exceeded

问题描述 今天idea构建一个新的项目时报错&#xff1a;GC overhead limit exceeded&#xff0c;错误是发生在编译阶段&#xff0c;而不是运行阶段。 ava: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceededat com.sun.tools.javac.resources…

如何编译OpenHarmony自带APP

作者&#xff1a;王石 概述 OpenHarmony 的主干代码是开源社区的重要学习资源&#xff0c;对于想进行应用开发和熟悉 OpenHarmony 能力的同学主干代码是非常重要的资源&#xff0c;在主干代码的 applications 目录里聚集了很多原生的应用实现&#xff0c;那么如何编译这些代码…

HashMap部分底层源码解析

哈希表的物理结构 HashMap底层都是哈希表&#xff08;也称散列表&#xff09;&#xff0c;线程不安全&#xff0c;其中维护了一个长度为2的幂次方的Entry类型的数组table&#xff0c;数组的每一个索引位置被称为一个桶(bucket)&#xff0c;你添加的映射关系(key,value)最终都被…

实战项目——智慧社区(三)之 门禁管理

1、人脸识别 实现思路 ①查询出所有的小区信息&#xff0c;下拉列表显示&#xff0c;用于后续判断人脸信息是否与所选小区匹配 ②人脸识别&#xff1a;调用腾讯人脸识别的API接口&#xff0c;首先判断传入图片是否为一张人脸&#xff1b;其次将这张人脸去服务器的人员库进行…

c++编程(3)——类和对象(1)、类

欢迎来到博主的专栏——c编程 博主ID&#xff1a;代码小豪 文章目录 类对象类的访问权限类的作用域 类 c最初对c语言的扩展就是增加了类的概念&#xff0c;使得c语言在原有的基础之上可以做到信息隐藏和封装。 那么我们先来讲讲“带类的c”与C语言相比有什么改进。 先讲讲类…

利用SARscape对日本填海造陆和天然气开采进行地表形变监测

日本千叶市&#xff0c;是日本南部重要的工业港市。位于西部的浦安市是一个典型的"填海造田"城市&#xff0c;东南部的东金区有一片天然气开采区域&#xff0c;本文利用SARscape&#xff0c;用干涉叠加的方法&#xff0c;即PS和SBAS&#xff0c;对这两个区域进行地表…

电磁兼容导论翻译疑问

在读电磁兼容导论P71页时&#xff0c;发现在“注意“这句话翻译的和原文有疑问&#xff1a;我的理解是单边幅度谱是双边幅度谱的两倍。请大家帮忙看看应如何翻译。 英文原版&#xff1a;Note that all positive frequency components except the dc component in the two-side…

红黑瓷砖(BFS和DFS)

9 6 ....#. .....# ...... ...... ...... ...... ...... #...# .#..#.45BFS import java.util.Deque; import java.util.LinkedList; import java.util.Scanner;public class Main {//. 黑色//# 红色// 黑色开始static final int N 11;static int n,m,ans 1; static char[][…

Learn SRP 01

学习链接&#xff1a;Custom Render Pipeline (catlikecoding.com) 使用Unity版本&#xff1a;Unity 2022.3.5f1 1.A new Render Pipeline 1.1Project Setup 创建一个默认的3D项目&#xff0c;项目打开后可以到默认的包管理器删掉所有不需要的包&#xff0c;我们只使用Unit…