C语言实现带头双向循环链表

news/2024/7/24 3:50:18 标签: c语言, 链表, 开发语言

文章目录

  • 写在前面
  • 1. 链表节点的定义
  • 2. 链表的初始化
  • 3. 插入数据
    • 3.1 头插
    • 3.2 尾插
    • 3.3 在指定位置的前面插入数据
  • 4 删除数据
    • 4.1 头删
    • 4.2 尾删
    • 4.3 删除指定位置的数据
  • 5 查找并修改数据
  • 5. 链表的销毁

写在前面

上面文章用C语言实现了单链表的增删查改,我们知道,链表只能从头结点开始正向遍历,而在单链表中插入或删除节点时,需要修改前一个节点的指针,因此在单链表中插入或删除节点时需要遍历链表找到前一个节点,导致插入和删除操作的效率较低。为了能够高效率解决类似的问题,本片文章继续用C语言来实现另一种线性存储结构——带头双向循环链表
我们从它的逻辑结构来更深层次的理解一下带头双向循环链表
在这里插入图片描述

1. 链表节点的定义

链表的结点分为三部分:指针域、数据域、指针域
指针域:用于指向当前节点的直接前驱节点
数据域:链表要存储的数据所在的区域。
指针域:用于指向当前节点的直接后继节点。

链表节点的逻辑图:
在这里插入图片描述
链表节点的定义:

typedef int STDataType;
typedef struct ListNode
{
	struct ListNode* prev;//指针域, 指向上一个节点
	struct ListNode* next;//指针域, 指向下一个节点
	STDataType val;//数据域
}LTNode;

2. 链表的初始化

链表在初始化的时候,只需要创建哨兵位的头节点即可,并将该节点的地址返回。该节点不存储有效数据,其prev 和 next指针指向自己。
在这里插入图片描述
由于后面的插入都需要创建新的节点,因此这里把创建节点封装成一个函数。

LTNode* BuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror(" BuyNode()");
	}

	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

链表的初始化代码如下:

LTNode* LTInit()
{
	//创建哨兵位的头节点
	LTNode* newnode = BuyNode(-1);
	
	//prev 和 next指针指向自己
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

3. 插入数据

链表插入数据时,根据插入位置的不同可以分为以下三种情况:

  • 在头节点前插入一个元素,即头插。
  • 链表中间位置插入元素。
  • 在最后一个节点后面插入一个元素,即尾插。

3.1 头插

头插数据步骤:

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的头部,更新指针。
    在这里插入图片描述
    代码如下:
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* newnode = BuyNode(x);//创建新节点

	LTNode* next = phead->next;

	//修改指针链接关系
	newnode->next = next;
	next->prev = newnode;

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

3.2 尾插

尾插数据步骤:

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的尾部,更新指针。

在这里插入图片描述
代码如下:

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* newnode = BuyNode(x);//创建新节点

	LTNode* tail = phead->prev;

	//修改指针链接关系
	newnode->prev = tail;
	tail->next = newnode;

	newnode->next = phead;
	phead->prev = newnode;
	//LTInsert(phead, x);
}

3.3 在指定位置的前面插入数据

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的 pos 位置之前,更新指针。
    在这里插入图片描述

代码如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//检查位置有效性
	LTNode* newnode = BuyNode(x);//创建新节点
	
	LTNode* posPrev = pos->prev;
	
	//修改指针链接关系
	pos->prev = newnode;
	newnode->next = pos;

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

4 删除数据

4.1 头删

头删的步骤如下:

  1. 判断链表是否为空,不为空在进行删除。
    判断链表是否为空的代码如下:
bool LTEmpty(LTNode* phead)
{
	return phead->next == phead;
}
  1. 删除第一个节点,并更新指针。

在这里插入图片描述

代码如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);//检查参数有效性
	assert(!LTEmpty(phead));//判断链表是否为空

	LTNode* pos = phead->next;
	LTNode* posNext = pos->next;

	free(pos);//删除第一个节点
	修改指针链接关系
	phead->next = posNext;
	posNext->prev = phead;
}

4.2 尾删

头删的步骤如下:

  1. 判断链表是否为空,不为空在进行删除。
  2. 删除最后一个节点,并更新指针。
    在这里插入图片描述
    代码如下:
void LTPopBack(LTNode* phead)
{
	assert(phead);//检查参数有效性
	assert(!LTEmpty(phead));//判断链表是否为空

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

	free(tail);//删除最后一个节点
	修改指针链接关系
	phead->prev = tailPrev;
	tailPrev->next = phead;
}

4.3 删除指定位置的数据

注意:删除指定位置的数据,需要传递正确的节点的地址,否则删除的结果是不确定的。

在这里插入图片描述
代码如下:

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

	free(pos);//删除指定位置的节点

	//修改指针链接关系
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

5 查找并修改数据

遍历链表若找到目标节点,就返回目标节点的地址,否则返回空指针(NULL)。
该函数兼并修改的功能,因为该函数返回的是目标节点的地址。
代码如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* cur = phead->next;
	//遍历链表
	while (cur != phead)
	{
		//找到返回,目标节点地址
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//未找到,返回NLL
	return NULL;
}

5. 链表的销毁

  1. 依次释放链表的每个节点。
  2. 释放哨兵位的头节点。

注意:链表销毁以后,要在函数外面将头指针置空(NULL),以免造成野指针的问题。

代码如下:

void LTDestroy(LTNode* phead)
{
	assert(phead);//检查参数有效性
	LTNode* cur = phead->next;

	//依次释放链表的每个节点
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);//释放哨兵位的头节点
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述


http://www.niftyadmin.cn/n/5196274.html

相关文章

解决龙芯loongarch64服务器编译安装Python后yum命令无法使用的问题“no module named ‘dnf‘”

引言 在使用Linux系统时,我们经常会使用yum来管理软件包。然而,有时候我们可能会遇到yum不可用的情况,其中一个原因就是Python的问题。本文将介绍Python对yum可用性的影响,并提供解决方案。 问题引发 正常情况下,安装linux系统后,yum命令是可用状态,升级Python版本后,…

【18年扬大真题】定义一个Point类,要求如下所述。(1)用构造函数初始化Point类的对象(2)定义函数Distance,计算平面上两点之间的距离

【18年扬大真题】定义一个Point类&#xff0c;要求如下所述。 &#xff08;1&#xff09;用构造函数初始化Point类的对象 &#xff08;2&#xff09;定义函数Distance&#xff0c;计算平面上两点之间的距离 #include<stdio.h> #include<math.h> typedef struct {d…

使用requests库设置no_proxy选项的方法

问题背景 在使用requests库进行HTTP请求时&#xff0c;如果需要使用爬虫IP服务器&#xff0c;可以通过设置proxies参数来实现。proxies参数是一个字典&#xff0c;其中包含了爬虫IP服务器的地址和端口号。然而&#xff0c;当前的requests库并不支持通过proxies参数来设置no_pr…

庖丁解牛:NIO核心概念与机制详解 05 _ 文件锁定

文章目录 Pre概述锁定文件 &#xff08;lock&#xff09;Code文件锁定和可移植性 Pre 庖丁解牛&#xff1a;NIO核心概念与机制详解 01 庖丁解牛&#xff1a;NIO核心概念与机制详解 02 _ 缓冲区的细节实现 庖丁解牛&#xff1a;NIO核心概念与机制详解 03 _ 缓冲区分配、包装和…

基于springboot实现应急救援物资管理系统项目【项目源码】计算机毕业设计

基于springboot实现应急救援物资管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&a…

利用python批量读取大量Excel表格文件中指定内容并汇总

工作中出现需要从大量Excel表格文件中读取指定单元格内容并汇总到一个表格中。上网搜索一下&#xff0c;利用Python的pandas模块可以快速实现。openpyxl也可以&#xff0c;不过为了快速&#xff0c;以能搜到的形成代码为优先。 简单修改别人代码&#xff0c;实现如下。 impor…

单节点服务架构

单节点的服务架构&#xff1a; LNMP l:lilnux系统 n:nginx静态页面&#xff0c;转发动态请求 m:mysql数据库&#xff0c;后端服务器&#xff0c;保存用户和密码信息&#xff0c;以及论坛的信息 p:PHP&#xff0c;处理动态请求&#xff0c;动态请求转发数据库&#xff0c;然…

【C++高阶(三)】AVL树深度剖析模拟实现

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; AVL树 1. 前言2. AVL树的概念以及特性3. AVL树模…