带头双向循环链表的增删查改

2023-11-08

带头双向循环链表的增删查改

简介

首先我们来看一下带头双向循环链表的结构示意图,在实际内存中并非是这样的结构,画图是为了我们能更好的理解链表

在这里插入图片描述

带头双向循环链表的结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。虽然这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了 。

结构体的创建和函数的声明

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

typedef int LTDataType;

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

// 创建一个新结点
LTNode* BuyListNode(LTDataType x);

// 初始化链表
LTNode* ListInit();

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

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

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

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

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

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

// 在POS之前插入数据
void LTInsert(LTNode* pos, LTDataType x);

// 删除pos位置的数据
void LTErase(LTNode* pos);

// 链表判空
bool LTEmpty(LTNode* phead);

// 链表大小
size_t LTSize(LTNode* phead);

// 链表销毁
void LTDestroy(LTNode* phead);

函数功能的实现

创建一个新结点

创建新结点在初始化和插入数据时都需要使用,选择使用malloc开辟空间是为了在程序运行整个过程中链表都不会因为出了函数作用域而销毁。

这里我们使用LTDataType代替int是方便后续代码的修改,只需在头文件定义中修改数据类型就可实现不同类型的链表。

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;

	return node;
}

初始化链表

初始化时与单链表稍有不同,初始化链表为空时需要创建一个哨兵位的头结点phead

头结点的prev要指向尾结点,尾结点的next要指向头结点phead,这样才会形成循环。

但我们在初始化的时候,链表中是没有数据的,那么phead头结点的prevnext应该如何初始化呢?

因此,在初始化链表时,我们需要将头结点phead中的prevnext都指向自己,这样就会形成循环。

在这里插入图片描述

LTNode* ListInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

打印链表

打印链表时从头结点的下一个结点phead->next开始向后遍历并打印,直到遍历到头结点处(phead->next == phead)时便停止遍历和打印。

void LTPrint(LTNode* phead)
{
	assert(phead);

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

尾插

尾插数据时,首先要寻找链表的尾结点tail,不论链表是否为空,我们都能通过phead->prev找到尾结点,然后将新结点插入尾结点之后即可。

在这里插入图片描述

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

	// 创建节点
	LTNode* newnode = BuyListNode(x);
	// 寻找尾节点
	LTNode* tail = phead->prev;
	// 插入数据
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}

尾删

尾删时,需要找到两个结点,尾结点tail尾结点的前一个结点tailPrev,将尾结点删除,再将phead->prev指向tailPrev,重新构成循环。

因为每一个结点都是malloc开辟的,在删除结点时还需要把它释放掉。

删除结点时,需要判断是否存在结点。

在这里插入图片描述

void LTPopBack(LTNode* phead)
{
	assert(phead);
	// 判断是否只有一个头结点
	assert(phead->next != phead);

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
}

头插

创建一个新结点newnode,把它插入phead->next处。

在这里插入图片描述

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

	LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

头删

头删和尾删类似,需要判断链表是否为空,把要删的结点phead->next和要删除结点的下一个结点phead->next->next找到,将头结点指向删除结点的后一个结点,记得free掉删除的结点,防止内存泄露。

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

	LTNode* cur = phead->next;
	LTNode* next = cur->next;
	free(cur);
	phead->next = next;
	next->prev = phead;
}

查找

遍历链表中的数据,如果匹配就返回当前结点的地址,如果没有找到返回NULL

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

在pos位置之前插入数据

在pos位置之前插入数据,找到pos位置之前的结点,创建新的结点插入pos之前。

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

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

我们之前写的头插和尾插函数,可以复用LTInsert()函数。

头插就相当于在头结点的下一个结点phead->next前插入一个数据。

LTInsert(phead->next, x);

尾插就相当于在头结点phead前插入一个数据。

LTInsert(phead,x);

删除pos位置的数据

删除pos位置的数据时,先要找到pos位置前后两个结点pos->prevpos->next,前后两个结点链接后free掉pos位置的结点。

void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

同样头删和尾删也可以复用LTErase()函数。

头删就相当于删除头结点的下一个结点phead->next

LTErase(phead->next);

尾删就就相当于删除头结点的前一个结点phead->prev

LTErase(phead->prev);

链表判空

如果phead->next == phead,那就说明链表中没有数据。

bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

链表大小

遍历链表,统计结点个数,并返回。

size_t LTSize(LTNode* phead)
{
	assert(phead);

	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

链表销毁

遍历销毁链表,这里要注意提前记录删除当前结点的下一个结点,如果直接释放,会导致无法找到后续结点,造成内存泄漏。

void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

演示案例

void TestList1()
{
	LTNode* phead = ListInit();
	// 尾插
	LTPushBack(phead, 1);
	LTPushBack(phead, 2);
	LTPushBack(phead, 3);
	LTPushBack(phead, 4);
	LTPushBack(phead, 5);
	LTPrint(phead);
	
	// 尾删
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);

	// 头插
	LTPushFront(phead, 1);
	LTPushFront(phead, 2);
	LTPushFront(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 5);
	LTPrint(phead);

	// 头删
	LTPopFront(phead);
	LTPrint(phead);
	LTPopFront(phead);
	LTPrint(phead);
}
int main()
{
	TestList1();

	return 0;
}

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带头双向循环链表的增删查改 的相关文章

  • E (1052) : DS树--带权路径和

    文章目录 一 题目描述 二 输入与输出 1 输入 2 输出 三 参考代码 一 题目描述 计算一棵二叉树的带权路径总和 即求赫夫曼树的带权路径和 已知一棵二叉树的叶子权值 该二叉树的带权路径和APL等于叶子权值乘以根节点到叶子的分支数 然后求
  • C语言,求取数组的序亏:已知一个整数数组,求出个数组中每个元素在整个 数组的排序。

    要求获取整数数组中每个元素的排序 可以使用以下方法 1 定义一个结构体数组 其中每个结构体包含数组元素的值和索引 2 遍历整数数组 将每个元素与其索引一起存储到结构体数组中 3 对结构体数组进行排序 按照元素的值进行升序排序 4 遍历排序后
  • 数组实现循环队列(增设队列大小size)

    目录 一 前言 1 如何实现循环 2 如何判断队列为空 3 如何判断队列为满 二 循环队列的结构定义 三 循环队列的创建及其初始化 四 入队 五 出队 六 取队头元素 七 取队尾元素 八 循环队列判空 九 循环队列判满 十 循环队列销毁 一
  • 【数据结构入门精讲 | 第二篇】一文讲清算法复杂度

    上篇文章中我们引入了算法 数据结构 数据类型等概念 而要想衡量一个算法与数据结构是否为优质的 就需要一个衡量标准 这个衡量标准也是在我们实现一个好的算法时要遵循的原则 目录 基本概念 渐进性态 渐进性态数学表征 算法复杂度的运算 顺序搜索算
  • 八大排序(希尔排序)

    上篇文章我们来看了看插入排序是怎么实现的 本章内容就是在插入排序的基础上完成希尔排序 希尔排序是一个比较强大的排序 我们希尔排序的时间复杂度是比较难算的 这里直接给出的结论就是时间复杂度就是O N 1 3 比较难算的原因就是我们每一次的次数
  • List去重-使用distinctByKey方法根据对象的属性进行去重

    description 使用distinctByKey方法根据对象的属性进行去重 author zs date 2023 12 18 14 29 param keyExtractor return java util function Pr
  • leetcode 560. 和为 K 的子数组(优质解法)

    代码 class Solution public int subarraySum int nums int k int length nums length key 表示前缀和 value 表示个数 HashMap
  • 数据结构算法-快速排序

    核心思路 快速排序算法核心思路 选择一个 基准 元素 将数组分为两个子数组 一个包含比基准小的元素 另一个包含比基准大的元素 然后对这两个子数组进行递归排序 基准数 初始化两个索引 i 和 j 分别子数组的开头和结尾 初始化基准元素 bas
  • 【华为OD】给定一个整数数组nums,请你在该数组中找出两个数,使得这两个数 的和的绝对值abs(nums[x] + nums[y])为最小值并按从小到大返回这 两个数以及它们和的绝对值

    题目描述 给定一个整数数组nums 请你在该数组中找出两个数 使得这两个数 的和的绝对值abs nums x nums y 为最小值并按从小到大返回这 两个数以及它们和的绝对值 每种输入只会对应一个答案 数组中同一 个元素不能使用两遍 输入
  • 【数据结构和算法】 K 和数对的最大数目

    其他系列文章导航 Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一 题目描述 二 题解 2 1 方法一 双指针排序 三 代码 3 1 方法一 双指针排序 3
  • LeetCode21. Merge Two Sorted Lists

    文章目录 一 题目 二 题解 一 题目 You are given the heads of two sorted linked lists list1 and list2 Merge the two lists into one sort
  • LeetCode 1901. 寻找峰值 II

    一 题目 1 题目描述 一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子 上 下 左 右 的元素 给你一个 从 0 开始编号 的 m x n 矩阵 mat 其中任意两个相邻格子的值都 不相同 找出 任意一个 峰值 mat i j
  • Leetcode 45 跳跃游戏 II

    题意理解 给定一个长度为 n 的 0 索引 整数数组 nums 初始位置为 nums 0 每个元素 nums i 表示从索引 i 向前跳转的最大长度 还是从初始坐标i 0的位置到达最后一个元素 但是问题不是能不能跳到 而是 最少几步能跳到最
  • Leetcode 55 跳跃游戏

    题意理解 非负整数数组 nums 最初位于数组的 第一个下标 数组中的每个元素代表你在该位置可以跳跃的最大长度 需要跳到nums最后一个元素即为成功 目标 是否能够跳到最后一个元素 解题思路 使用贪心算法来解题 需要理解局部解和最优解的关系
  • LeetCode 2397. 被列覆盖的最多行数,状态压缩优化回溯法

    一 题目 1 题目描述 给你一个下标从 0 开始 大小为 m x n 的二进制矩阵 matrix 另给你一个整数 numSelect 表示你必须从 matrix 中选择的 不同 列的数量 如果一行中所有的 1 都被你选中的列所覆盖 则认为这
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数
  • 链表的中间节点

    链表的中间节点 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 备战技术面试 力扣提供海量技术面试资源 帮助你高效提升编程技能 轻松拿下世界 IT 名企 Dream Offer https leetcode cn problems
  • 矩阵基本操作3

    题目描述 问题描述 定义一个N M N M lt 100 的矩阵 将一个该矩阵的行和列的元素互换 存到另一个二维数组中 输入格式 一行两个整数 N M 中间用空格隔开 表示矩阵有N行 M列 接下来共N行M列表示矩阵 输出格式 输出转置以后的
  • 数据结构——排序

    前言 哈喽小伙伴们好久不见 也是顺利的考完试迎来了寒假 众所周知 不怕同学是学霸 就怕学霸放寒假 假期身为弯道超车的最佳时间 我们定然是不能懒散的度过 今天我们就一起来学习数据结构初阶的终章 七大排序 本文所有的排序演示都为升序排序 目录
  • 单向不带头链表的使用

    单向不带头链表的使用 链表的创建 typedef struct LNode SLDataType data struct LNode next LNode LinkList 按位查找 LNode GetElem LinkList L int

随机推荐

  • 初学(7)——Hadoop错误:can‘t create 事务 lock on /var/lib/rpm/.rpm.lock (权限不够)

    执行删除操作时出现错误 权限不够 1 使用sudo命令 2 如果出现上述情况 切换至root用户 将要执行该操作的用户添加到sudoers su vim etc sudoers xxx 要添加的用户 ALL ALL ALL 命令成功执行
  • Mac用iTerm2连接到Linux上,不能输入中文

    服务器是ubuntu 用Mac的iterm2 ssh连上去 终端显示中文乱码 也不能输入中文 然而本地终端可以显示和输入 解决方法 这种情况一般是终端和服务器的字符集不匹配 MacOSX下默认的是utf8字符集 输入locale可以查看字符
  • 步进电机实验

    通过 ULN2003 驱动模块控制 28BYJ48 步进电机运行方向和速度 按下 KEY1 键调节电机旋转方向 按下 KEY2 键 电机加速 当按下 KEY3 键 电机减速 实现对步进电机运动的简单控制 步进电机简介 步进电机是将电脉冲信号
  • java中io各种流的关闭顺序

    还是先看API void close Closes this stream and releases any system resources associated with it close void close throws IOExc
  • 【react从入门到精通】初识React

    文章目录 人工智能福利文章 前言 React技能树 什么是 React 安装和配置 React 创建 React 组件 渲染 React 组件 使用 JSX 传递属性 Props 处理组件状态 State 处理用户输入 事件处理 组合和嵌套
  • 生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼

    说到生成模型 VAE GAN可谓是 如雷贯耳 本站也有过多次分享 此外 还有一些比较小众的选择 如flow模型 VQ VAE等 也颇有人气 尤其是VQ VAE及其变体VQ GAN 近期已经逐渐发展到 图像的Tokenizer 的地位 用来直
  • Mac环境下安装、配置cocos2d-x环境

    转自 https www jianshu com p 2649a88b7f4d Mac环境下安装 配置cocos2d x环境 玉米包谷关注 2017 09 23 11 16 07字数 207阅读 2 359 1 将cocos2d x下载到桌
  • 学习笔记:进程间通信

    进程间通信的方式 示例 管道的概念及使用 管道pipe详解点击此处 示例 注 写端fd 1 读端fd 0 创建共享内存 mmap函数 思考 总结 示例 mmap父子进程通信 匿名映射 示例 进程1 进程二 shmget函数和mmap的区别
  • 逆矩阵的概念与性质

    逆矩阵的概念与性质 定理1 若矩阵A是可逆的 则A的逆矩阵是唯一的 并记作A的逆矩阵为A 1 性质 定理3 设A为n阶矩阵 则下列各命题等价 1 A是可逆的 2 AX 0只有零解 3 A与I行等价 4 A可表为有限个初等矩阵的乘积 注意 初
  • Docker的概念(1)

    前置条件 需要掌握Linux及常用命令 目录 1 Docker是什么 2 Docker用途 3 Docker与虚拟化 4 Docker和一个正常的虚拟机有何区别 5 Docker虚拟化的好处 5 1 应用部署方便 5 2 服务器同等配置 性
  • NDK开发——FFmpeg实现视频转YUV、视频转RGB显示、音频转PCM、音频播放、音视频同步

    项目演示 前提准备 编译FFmpeg CMake并能运行 详细可见我博客 下载libyuv库并编译成libyuv so库 用于实现转换RGB格式功能 FFmpeg库简介 avcodec 编解码 包含 avformate 封装格式处理 avf
  • Linux makefile 教程 非常详细,且易懂

    最近在学习Linux下的C编程 买了一本叫 Linux环境下的C编程指南 读到makefile就越看越迷糊 可能是我的理解能不行 于是google到了以下这篇文章 通俗易懂 然后把它贴出来 方便学习 后记 看完发现这篇文章和 Linux环境
  • 烟雾调节器

    先看效果 烟雾调节展示 再看代码
  • 【深度学习基础】损失函数

    深度学习基础 性能评估指标 超参数介绍 损失函数 前言 本文主要总结一下常见目标检测的损失函数以及一些基础的函数 主要损失函数为mask rcnn涉及到的损失函数包括 MSE均方误差损失函数 Cross Entropy交叉熵损失函数 目标检
  • Java项目:设计管理系统(java+SSM+JSP+MYSQL+layui+Maven)

    源码获取 博客首页 资源 里下载 一 项目简述 功能包括 课题管理 学生管理 内容管理 文件管理 提问管理 教师管理 进度管理等等 二 项目运行 环境配置 Jdk1 8 Tomcat8 5 mysql Eclispe IntelliJ ID
  • 新规明确所有APP必须备案!附备案指引

    2023年8月底前 新APP需要先备案才能上线 2024年4月前 存量APP完成备案 未履行备案手续的 不得从事APP互联网信息服务 网络接入服务提供者 分发平台 智能终端生产企业不得为未履行备案手续的APP提供网络接入 分发 预置等服务
  • SQL server 安装问题

    安装sqlserver2008中遇到的一些问题和解决办法 安装过程中遇到restart computer 导致不能成功安装 解决办法 1 打开注册表编辑器 2 找到以下路径 HKEY LOCAL MACHINE SYSTEM Current
  • 2020蓝桥杯省赛Java B组一等奖

    大家觉得写还可以 可以点赞 收藏 关注一下吧 也可以到我的个人博客参观一下 估计近几年都会一直更新 和我做个朋友吧 https motongxue cn 文章目录 A 门牌制作 问题描述 答案提交 代码 B 寻找 2020 问题描述 答案提
  • php用session防CC

  • 带头双向循环链表的增删查改

    带头双向循环链表的增删查改 简介 首先我们来看一下带头双向循环链表的结构示意图 在实际内存中并非是这样的结构 画图是为了我们能更好的理解链表 带头双向循环链表的结构最复杂 一般用在单独存储数据 实际中使用的链表数据结构 都是带头双向循环链表