数据结构第六弹---带头双向循环链表

news2024/9/23 1:25:27

双向循环链表

  • 1、带头双向循环链表概念
  • 2、带头双向循环链表的优势
  • 3、带头双向循环链表的实现
    • 3.1、头文件包含和结构定义
    • 3.2、创建新结点
    • 3.3、打印
    • 3.4、初始化
    • 3.5、销毁
    • 3.6、尾插
    • 3.7、头插
    • 3.8、头删
    • 3.9、尾删
    • 3.10、查找
    • 3.11、在pos之前插入
    • 3.12、删除pos位置
    • 3.13、判断是否为空
    • 3.14、计算大小
  • 4、代码汇总
  • 总结

1、带头双向循环链表概念

在这里插入图片描述

概念:带头双向循环链表是一种特殊类型的链表,它由一系列节点组成,每个
节点包含一个数据域和两个指针域,第一个结点不存储有效数据。其中一个指
针指向下一个节点,另一个指针指向前一个节点。在带头双向循环链表中,首
节点的前一个节点是尾节点,尾节点的下一个节点是首节点,形成一个闭环。

2、带头双向循环链表的优势

1.高效遍历:由于带头双向循环链表可以双向遍历,因此可以在O(1)时间内访问任何节点。
2.内存高效:与双向链表相比,带头双向循环链表不需要额外的内存来存储头部节点。
3.插入和删除操作高效:在带头双向循环链表中插入和删除节点时,只需调整指针即可,无需移动大量数据。

3、带头双向循环链表的实现

实现一个带头双向循环链表首先得创建一个工程。(下图为vs 2022)
在这里插入图片描述

List.h(带头双向循环链表的类型定义、接口函数声明、引用的头文件)
List.c(带头双向循环链表接口函数的实现)
test.c (主函数、测试顺序表各个接口功能)
以下是List.h的代码。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

//双向链表打印
void ListPrint(ListNode* phead);
//双向链表初始化
ListNode* ListInit();
//双向链表销毁
void ListDestory(ListNode* phead);
//双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
//头插
void ListPushFront(ListNode* phead, LTDataType x);
//头删
void ListPopFront(ListNode* phead);
//尾删
void ListPopBack(ListNode* phead);
//查找
ListNode* ListFind(ListNode* phead, LTDataType x);
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);
//删除pos位置
void ListErase(ListNode* pos);
//判断是否为空
bool ListEmpty(ListNode* phead);
//计算大小
int ListSize(ListNode* phead);

3.1、头文件包含和结构定义

以下是实现双向循环链表可能用到的头文件。

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

以下是博主创建的双向循环链表的结构,可以根据自己的喜好创建喔。
建议:创建结构时最好能通俗易懂,最好不用拼音创建。

typedef int LTDataType;//定义数据类型,可以根据需要更改
typedef struct ListNode
{
	LTDataType data;      //数据域 存储数据
	struct ListNode* next;//指针域 存储指向下一个结点的指针
	struct ListNode* prev;//指针域 存储指向前一个结点的指针
}ListNode;

3.2、创建新结点

为什么先创建新结点而不是初始化呢?因为当前链表为带头的链表,初始化时需要创建结点,所以就先封装创建结点函数。
在这里插入图片描述

ListNode* BuyList(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

3.3、打印

在这里插入图片描述

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

3.4、初始化

在这里插入图片描述

ListNode* ListInit()
{
	ListNode* phead = BuyList(0);

	phead->next = phead;//构成循环
	phead->prev = phead;//构成循环

	return phead;
}

3.5、销毁

在这里插入图片描述

void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;//养成好习惯,释放之后手动置为NULL
}

3.6、尾插

在这里插入图片描述

void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	//1.创建结点
	ListNode* newnode = BuyList(x);
	ListNode* tail = phead->prev;//先找到尾结点
    
    //2.链接next
	tail->next = newnode;
	newnode->prev = tail;
    //3.链接prev
	newnode->next = phead;
	phead->prev = newnode;

}

尾插测试
建议养成有初始化函数就有销毁函数的习惯。
在这里插入图片描述

3.7、头插

在这里插入图片描述

void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyList(x);
	ListNode* first = phead->next;
	
	phead->next = newnode;
	newnode->prev = phead;

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

头插测试
在这里插入图片描述

3.8、头删

在这里插入图片描述

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//没有数据则报错
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
}

测试头删
在这里插入图片描述
在这里插入图片描述

3.9、尾删

在这里插入图片描述

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* prev = tail->prev;

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

	free(tail);
	tail = NULL;
}

尾删测试
在这里插入图片描述

3.10、查找

思想:遍历一遍链表,如果该结点的data等于x则返回该结点的地址,遍历一遍没有找到则返回NULL,跟后面在pos位置插入函数结合起来用。

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.11、在pos之前插入

跟头插尾插思想差不多,可以自己画图理解理解喔,如果有不理解的可以私信博主喔!这里就没有画图啦!

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyList(x);
	ListNode* prev = pos->prev;

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

测试
在这里插入图片描述

3.12、删除pos位置

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = pos->next;
	next->prev = prev;
}

在这里插入图片描述

3.13、判断是否为空

bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;//相等则为真,不相等则为假
}

在这里插入图片描述

3.14、计算大小

思想:创建一个size变量,从头结点的下一个结点遍历链表,不等于头结点则将size++。

int ListSize(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	int size = 0;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

测试
在这里插入图片描述

4、代码汇总

以下是SList.c的代码

//创建结点
ListNode* BuyList(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
//打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
//初始化
ListNode* ListInit()
{
	ListNode* phead = BuyList(0);

	phead->next = phead;//构成循环
	phead->prev = phead;//构成循环

	return phead;
}
//销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;//养成好习惯,释放之后手动置为NULL
}
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyList(x);
	ListNode* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
}
//头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyList(x);
	ListNode* first = phead->next;
	
    phead->next = newnode;
	newnode->prev = phead;

	newnode->next = first;
	first->prev = newnode;

}
//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
}
//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* prev = tail->prev;

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

	free(tail);
	tail = NULL;
}
//查找元素为X的地址
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyList(x);
	ListNode* prev = pos->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
//删除pos位置
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = pos->next;
	next->prev = prev;
}
//判断是否为空
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	//1.
	//if (phead->next == phead)
	//{
	//	return true;
	//}
	//else
	//{
	//	return false;
	//}
	//2.
	return phead->next == phead;
}
//获取有效数据个数
int ListSize(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	int size = 0;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

总结

本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

苦学golang半年,写了一款web服务器

苦学golang半年&#xff0c;写了一款web服务器 文章目录 苦学golang半年&#xff0c;写了一款web服务器example 项目地址&#xff1a;https://github.com/fengyuan-liang/jet-web-fasthttp 苦学golang半年&#xff0c;写了一款web服务器&#xff0c;里面包含笔者各种工程实践&a…

“火火的”动态(myBlink of csdn)

集结我的人气Blink索引列表&#xff0c;Python脚本自动生成于2024年01月06日。 生成本篇笔记Html超文本的Python脚本源码地址&#xff1a;#codes (本笔记适合初通Python&#xff0c;熟悉六大基本数据类型(str字符串、int整型、float浮点型、list列表、tuple元组、set集合、dict…

【C程序设计】C数组

C 语言支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 runoob0、runoob1、...、runoob99&#xff0c;而…

[SwiftUI]工程最低适配iOS13

问题&#xff1a; 新建工程&#xff0c;选择最低支持iOS13报错&#xff1a; main() is only available in iOS 14.0 or newer Scene is only available in iOS 14.0 or newer WindowGroup is only available in iOS 14.0 or newer 解决&#xff1a; 注释掉上面代码&#x…

栈的数据结构实验报告

一、实验目的&#xff1a; 1、理解栈的定义&#xff1b; 2、利用栈处理实际问题。 二、实验内容&#xff08;实验题目与说明&#xff09; 利用栈实现数据的分类&#xff0c;将输入的整数以奇偶为标准分别存放到两个栈中&#xff0c;并最终从栈1和栈2输出偶数和奇数序列。 …

国内代理IP推荐!

国内代理IP&#xff0c;日更新50w IP&#xff0c;全国200城市&#xff0c;可利用率高达99%。提供HTTP/HTTPS/SOCKS5协议&#xff0c;满足数据采集、爬虫业务需求。丰富的api参数选择&#xff0c;可自由选择时效地区等&#xff0c;按需求过滤重复资源。低延迟&#xff0c;提供丰…

STM32学习笔记二十一:WS2812制作像素游戏屏-飞行射击游戏(11)探索游戏脚本

还记得上次在第十七章中为BOSS创建的路径动画吧。我们写了一大坨的代码来描述BOSS的运动路径&#xff0c;但凡是写过几年代码的人都不会干出这样的事情。-_-! 没办法&#xff0c;谁叫那时候还没有脚本呢。这章就来补齐这块短板。 脚本属于配置化的一种&#xff0c;你可以把脚…

XSKY SDS 产品率先获得 OceanBase V4 新版本认证

近日&#xff0c;北京奥星贝斯科技有限公司&#xff08;简称&#xff1a;OceanBase&#xff09;与北京星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY 星辰天合&#xff09;顺利完成产品兼容性认证。 XSKY 的高性能全闪存储以及混闪存储&#xff0c;与 OceanBase V…

LayaAir3最稳定的版本来了,3.0.11或将成为3.0分支的终结版

在LayaAir1与LayaAir2的发版策略中&#xff0c;存在一个BUG&#xff0c;就是永远都在新的版本上修复上一个版本的问题。这就导致了在高速迭代的过程中&#xff0c;很难有一个持续维护的小版本分支。也就是说直到引擎停止了功能更新的最后几个版本&#xff0c;才能真正步入稳定版…

leetcode算法题之递归--深度优先搜索总结

文章目录 1.全排列2.子集 1.全排列 全排列 class Solution {vector<vector<int>> ret;vector<int> path;bool check[7];//标记nums数组某个下标是否已访问&#xff0c;剪枝使用 public:vector<vector<int>> permute(vector<int>& n…

工智能基础知识总结-- 什么是Seq2Seq

什么是Seq2Seq Seq2Seq模型,全称Sequence to sequence,由Encoder和Decoder两个部分组成,每部分都是一个RNNCell(RNN、LSTM、GRU等)结构。Encoder将一个序列编码为一个固定长度的语义向量,Decoder将该语义向量解码为另一个序列。输入序列和输出序列都可以是不定长序列。 S…

124基于matlab的禁忌搜索算法和蚁群优化算法优化TSP路径

基于matlab的禁忌搜索算法和蚁群优化算法优化TSP路径&#xff0c;动态输出路径规划过程及输出最小距离。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。需要直接拍下&#xff0c;拍下后发邮箱。标价为程序价格&#xff0c;不包含售后。程序保证可直接运行。 …

18款Visual Studio实用插件(更新)

前言 俗话说的好工欲善其事必先利其器&#xff0c;安装一些Visual Studio实用插件对自己日常的开发和工作效率能够大大的提升&#xff0c;避免996从选一款好的IDE实用插件开始。以下是我认为比较实用的Visual Studio插件希望对大家有用&#xff0c;大家有更好的插件推荐可在文…

引导过程的解析以及如何利用systemd去管理nginx

bios加电自检------mbr--------grub-------加载内核文件------启动第一个进程 bios的主要作用&#xff1a;检测硬件是否正常&#xff0c;然后根据bios中的启动项设置&#xff0c;去找内核文件 boot开机启动项顺序&#xff0c;你可以把内核文件放在何处&#xff1f; 1.硬盘 …

什么是谐波减速机?日本Harmonic哈默纳科谐波减速机有哪些优点?

一、什么是谐波减速机&#xff1f; 谐波减速装置最早期被叫做“strain wave gearing”&#xff0c;直译过来为“应变波齿轮”。其后被HarmonicDrive Systems 公司大规模商业实用化后&#xff0c;经过二次翻译后&#xff0c;中文名称才将其称为“谐波齿轮传动”。 谐波减速机是…

免费的GPT4来了,你还不知道吗?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

odoo17 | 视图操作按钮

前言 到目前为止&#xff0c;我们主要通过声明字段和视图来构建我们的模块。在上一章中&#xff0c;我们刚刚通过计算字段和onchanges引入了业务逻辑。在任何真实的业务场景中&#xff0c;我们都会希望将一些业务逻辑链接到操作按钮。在我们的房地产示例中&#xff0c;我们希望…

【数据分析】指数移动平均线的直观解释

slavahead 一、介绍 在时间序列分析中&#xff0c;通常需要通过考虑先前的值来了解序列的趋势方向。序列中下一个值的近似可以通过多种方式执行&#xff0c;包括使用简单基线或构建高级机器学习模型。 指数&#xff08;加权&#xff09;移动平均线是这两种方法之间的稳健权衡。…

FreeRTOS——内存管理知识总结及实战

1 freeRTOS动态创建与静态创建 动态创建&#xff1a;从FreeRTOS 管理的内存堆中申请创建对象所需的内存&#xff0c;在对象删除后&#xff0c; 这块内存释放回FreeRTOS管理的内存堆中 静态创建&#xff1a;需用户提供各种内存空间&#xff0c;并且使用静态方式占用的内存空间一…