来领略一下带头双向循环链表的风采吧

news2024/11/25 12:52:15

🍉 博客主页:阿博历练记
📖文章专栏:数据结构与算法
🚍代码仓库:阿博编程日记
🌹欢迎关注:欢迎友友们点赞收藏+关注哦

在这里插入图片描述

文章目录

    • 🍄前言
    • 🍼双向循环链表
      • 🔍1.链表的定义
      • 🔍2.链表的初始化
      • 📢误区
      • 🔍3.链表的尾插
      • 🔍4.链表的头插
      • 📢注意先后顺序
      • 🔍5.链表的打印
      • 🔍6.链表的尾删
      • 🔍7.链表的头删
      • 🔍8.链表的查找
      • 🔍9.链表任意位置的插入(在pos之前插入)
      • 📢注意先后顺序
      • 🔍10.链表任意位置的删除(pos位置)
      • 🔍11.链表的销毁
      • 👻List.h代码
      • 👻List.c代码
      • 👻test.c代码
      • 🧋代码效果展示

🍄前言

在这里插入图片描述
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
上期阿博带友友们实现了单链表,我们可以看出单链表的尾插以及尾删都是非常复杂的,比如尾插如果链表为空,我们就需要调用二级指针改变来接收plist的地址,因为首次尾插,plist为空,我们要让plist指向结点,需要改变plist,所以传plist的地址,但是之后,我们只需要改变结构体中的next指针,让它指向我们开辟出来的结点就可以了,此时我们改变的是结构体,就不需要传结构体地址了。再比如我们的尾删我们需要遍历链表,找到尾结点的前一个结点,这样非常麻烦,而且效率很低,但是双向循环链表就会方便很多,我们可以直接通过尾结点的prev指针找到它的前一个结点,我们尾插的时候就不用再判断链表是否为空了,因为就算链表为空,头、尾结点也不为空,它们此时都是哨兵位,我们尾插的时候改变的一直都是结构体的next指针.
在这里插入图片描述

🍼双向循环链表

🔍1.链表的定义

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

🔍2.链表的初始化

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return  NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return  newnode;
}
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return  phead;
}

📢误区

❌错误案例

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return  NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return  newnode;
}
void LTInit(LTNode*phead)
{
    phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
}

在这里插入图片描述

⛳友友们,这里我们通过调试也可以看出plist的值并没有被改变,因为我们传的是plist的值,所以形参的改变不会影响实参,形参只是实参的一份临时拷贝,这里我们要改变的是plist,所以我们要传plist的地址,但是我们还可以通过通过返回值的方式来让plist接收newnode,这种就是值拷贝的方式返回,就是先把newnode的值拷贝给pilst,然后它又是一个局部变量,出完函数作用域就销毁了,但是我们通过plist找到那个开辟出来的结点因为它在栈区上存放,所以出完函数作用域,那个结点并没有销毁.

🔍3.链表的尾插

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return  NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return  newnode;
}
void  PushBack(LTNode* phead, LTDataType x)
{
	assert(phead);   //就算链表为空,这里也一定不能为空,因为phead是带哨兵位的头结点。
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
	//LTInsert(phead,x);
}

在这里插入图片描述

🔍4.链表的头插

1.未定义指针版

void  PushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);

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

📢注意先后顺序

在这里插入图片描述
2.定义指针版

void  PushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
	//LTInsert(phead->next,x);
}

在这里插入图片描述

🔍5.链表的打印

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

在这里插入图片描述

🔍6.链表的尾删

bool  LTEmpty(LTNode* phead)
{
	assert(phead);
	return  phead->next == phead;
}
void  PopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	//LTErase(phead->prev);
}

在这里插入图片描述

所以友友们这里我们就可以定义一个布尔值,来判空,这样可以提高我们代码的可读性💯.

bool  LTEmpty(LTNode* phead)
{
	assert(phead);
	return  phead->next == phead;
}

友友们注意这里是个等号,不是赋值号,因为空链表是phead->next和phead相等,而不是赋值.

🔍7.链表的头删

void  PopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);
	//LTErase(phead->next);
}

在这里插入图片描述
这里我们也可以定义两个指针来提高我们代码的可读性🙈🙈🙈.
在这里插入图片描述

🔍8.链表的查找

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

友友们注意,这里我们让cur从phead->next处开始遍历,因为哨兵位的头结点不存储有效数据.这个函数同时兼具修改的功能,因为它可以返回结点的地址.

🔍9.链表任意位置的插入(在pos之前插入)

1.未定义指针版

void  LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

📢注意先后顺序

在这里插入图片描述
2.定义指针版
在这里插入图片描述

友友们,当我们这个函数写好之后,我们的头插尾插就可以附用这个函数了,头插就可以写成LTInsert(phead->next,x),尾插就可以写成LTInsert(phead,x),⛳友友们一定要注意尾插不是LTInsert(phead->prev,x),因为我们是在pos之前插入的,所以我们pos的位置必须是头结点.

🔍10.链表任意位置的删除(pos位置)

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

在这里插入图片描述

友友们,当我们这个函数写好之后,我们的头删尾删就可以附用这个函数了,头删就可以写成LTErase(phead->next),尾删就可以写成LTErase(phead->prev).

🔍11.链表的销毁

void  LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		//cur = cur->next;   这里cur已经被释放了,不能在使用了,否则就是野指针
		cur = next;
	}
	free(phead);
	//phead = NULL;   这里置空也没用,因为phead是一级指针,形参的改变不会影响实参,就算把它置空,plist也不会被改变.
}

友友们注意,这里phead=NULL对plist是无效的,因为它们是值传递,形参只是实参的一份临时拷贝,形参的改变不会影响实参.所以我们可以在外面把plist置空,当我们头插,尾插之后一定要注意及时释放,否则就会出现内存泄露.

👻List.h代码

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

typedef  int  LTDataType;
typedef struct  ListNode
{
	LTDataType  data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;
LTNode* LTInit();    //初始化链表
void  LTPrint(LTNode* phead);   //打印链表
void  PushBack(LTNode* phead, LTDataType x);   //尾插
void  PushFront(LTNode* phead, LTDataType x);  //头插
bool  LTEmpty(LTNode* phead);
void  PopFront(LTNode* phead);        //头删
void  PopBack(LTNode* phead);         //尾删
LTNode* LTFind(LTNode* phead, LTDataType x);  //链表的查找
//在pos之前插入
void  LTInsert(LTNode* pos, LTDataType x);
void  LTErase(LTNode* pos);  //删除pos位置的值
void  LTDestroy(LTNode* phead);  //链表的销毁

👻List.c代码

#define  _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return  NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return  newnode;
}

LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return  phead;
}
void  LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("guard<==>");
	while (cur != phead)
	{
		printf("%d<==>",cur->data);
		cur = cur->next;
	}
	printf("\n");
}
void  PushBack(LTNode* phead, LTDataType x)
{
	assert(phead);   //就算链表为空,这里也一定不能为空,因为phead是带哨兵位的头结点。
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
	//LTInsert(phead,x);
}
void  PushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);

	/*newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;*/
	//这里我们也可以提前记录下一结点的地址,这样就不用在意顺序了.
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
	//LTInsert(phead->next,x);
}
bool  LTEmpty(LTNode* phead)
{
	assert(phead);
	return  phead->next == phead;
}
void  PopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);
	//LTErase(phead->next);
}
void  PopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	//LTErase(phead->prev);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return  cur;
		}
		cur = cur->next;
	}
	return  NULL;
}
void  LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
void  LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}
void  LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		//cur = cur->next;   这里cur已经被释放了,不能在使用了,否则就是野指针
		cur = next;
	}
	free(phead);
	//phead = NULL;   这里置空也没用,因为phead是一级指针,形参的改变不会影响实参,就算把它置空,plist也不会被改变.
}

👻test.c代码

#define  _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void  TestList1()
{
	LTNode* plist = LTInit();
	PushBack(plist, 1);
	PushBack(plist, 2);
	PushBack(plist, 3);
	PushBack(plist, 4);
	LTPrint(plist);

	PopBack(plist);
	LTPrint(plist);

	PopBack(plist);
	LTPrint(plist);
	LTDestroy(plist);
	plist = NULL;
}
void TestList2()
{

	LTNode* plist = LTInit();
	PushFront(plist, 1);
	PushFront(plist, 2);
	PushFront(plist, 3);
	PushFront(plist, 4);
	PopFront(plist);
	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTInsert(pos, 30);
	}
	LTPrint(plist);
	LTDestroy(plist);
	plist = NULL;
}
int  main()
{
	TestList1();
	TestList2();
	return  0;
}

🧋代码效果展示

在这里插入图片描述

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

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

相关文章

Qt使用星空图作为窗口背景,点击键盘的WASD控制小飞机在上面移动。

事件函数的使用依托于Qt的事件机制&#xff0c;一个来自于外部事件的传递机制模型如下所示 信号槽虽然好用&#xff0c;但是无法包含所有的情况&#xff0c;事件函数可以起到对信号槽无法覆盖的一些时机进行补充&#xff0c;事件函数的使用无需连接。 常用的事件函数如下所示。…

设计模式5—抽象工厂模式

5.抽象工厂模式 概念 抽象工厂模式&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定他们具体的类。抽象工厂又称为Kit模式&#xff0c;属于对象创建型模式。 抽象工厂可以将统一产品族的单独工厂封装起来&#xff0c;在正常使用中&#xff0c…

计算机网络笔记——网络层、传输层、应用层(方老师408课程)(持续更新)

文章目录 前言网络层网络层提供的两种服务网际协议——IP虚拟互联网络IP数据报格式逐一理解整体理解IP数据报分片与长度精算 IP地址IP地址概述分类的IP地址——ABCDE分类IP的子网划分不分类的IP地址——CIDRIP地址总结 IP分组的转发网际控制报文协议——ICMP下一代网络协议——…

我用 ChatGPT 干的 18 件事!【人工智能中文站创始人:mydear麦田访谈】

新建了一个网站 https://ai.weoknow.com/ 每天给大家更新可用的国内可用chatGPT 你确定你可以使用ChatGPT吗&#xff1f; 今天我整理了18种ChatGPT的使用方法&#xff0c;让大家看看你可以使用哪些。 1.语法修正 2.文本翻译 3.语言转换 4.代码解释 5.修复代码错误 6.作为百科…

初识HTML的基础知识点!!!

初识HTML&#xff01;&#xff01;&#xff01; 一、系统构架 1.B/S构架 &#xff08;1&#xff09;B/S构架&#xff08;Browser / Server) 就是&#xff08;浏览器/服务器的交互形式&#xff09; Browser支持HTML、CSS、JavaScript &#xff08;2&#xff09;优缺点 优点…

UI--基本组件

目录 1. Designer 设计师 2. Layout 布局 3. 基本组件 3.1 QWidget 3.2 ui指针 3.3 QLabel 标签&#xff08;掌握&#xff09; 示例代码&#xff1a; dialog.h dialog.cpp 3.4 QAbstractButton 按钮类&#xff08;掌握&#xff09; 示例代码&#xff1a; dialog.ui dialog.h di…

【MyBaits】SpringBoot整合MyBatis之动态SQL

目录 一、背景 二、if标签 三、trim标签 四、where标签 五、set标签 六、foreach标签 一、背景 如果我们要执行的SQL语句中不确定有哪些参数&#xff0c;此时我们如果使用传统的就必须列举所有的可能通过判断分支来解决这种问题&#xff0c;显示这是十分繁琐的。在Spring…

linux查看服务端口号、查看端口(netstat、lsof)以及PID对应服务

linux查看服务端口号、查看端口&#xff08;netstat、lsof&#xff09; netstat - atulnp会显示所有端口和所有对应的程序&#xff0c;用grep管道可以过滤出想要的字段 -a &#xff1a;all&#xff0c;表示列出所有的连接&#xff0c;服务监听&#xff0c;Socket资料 -t &…

说服审稿人,只需牢记这 8 大返修套路!

本文作者&#xff1a;雁门飞雪 如果说科研是一场修炼&#xff0c;那么学术界就是江湖&#xff0c;投稿就是作者与审稿人或编辑之间的高手博弈。 在这一轮轮的对决中&#xff0c;有时靠的是实力&#xff0c;有时靠的是技巧&#xff0c;然而只有实力和技巧双加持的作者才能长久立…

Qt--项目打包

项目打包 一款正常的软件产品应该在任何的计算机中运行&#xff0c;不需要单独安装Qt的开发环境&#xff0c;因此需要把之前的项目打包成一个安装包。 1. 设置应用图标 设置应用程序图标的操作步骤如下所示。 1. 下载一个图标图片&#xff0c;格式要求png。&#xff08;png包含…

学习Python的day.13

输入和输出 一、输入 标准输入&#xff1a;从键盘输入 input(promptNone) # prompt: 输入的提示符,可以为空 Read a string from standard input --- 译为&#xff1a;从标准输入读入一个字符串&#xff0c;输入读取的一定是字符串&#xff0c;返回值就是一个字符串 那我们…

基于知识图谱的个性化学习资源推荐系统的设计与实现(论文+源码)_kaic

摘 要 最近几年来&#xff0c;伴随着教育信息化、个性化教育和K12之类的新观念提出,一如既往的教育方法向信息化智能化的转变&#xff0c;学生群体都对这种不受时间和地点约束的学习方式有浓厚的兴趣。而现在市面上存在的推荐系统给学生推荐资料时不符合学生个人对知识获取的…

多态与虚函数

多态与虚函数 多态的引入多态与虚函数多态编译时多态运行时多态 多态的原理静态联编和动态联编 多态的引入 学过C继承的话应该都知道在继承中存在一种菱形继承&#xff0c;假设存在一个类&#xff08;person&#xff09;&#xff0c;其派生出两个子类&#xff0c;分别是studen…

Template Method模式

文章目录 &#x1f4a1;前言分类优点 &#x1f4a1;问题引入&#x1f4a1;概念&#x1f4a1;例子&#x1f4a1;总结 &#x1f4a1;前言 此文是第一篇讲解设计模式的文章&#xff0c;而笔者我又不想另起一篇来概述设计模式的分类&#xff0c;作用&#xff0c;以及优点&#xff…

MySQL笔记(四) 函数、变量、存储过程、游标、索引、存储引擎、数据库维护、指定字符集、锁机制

MySQL笔记&#xff08;四&#xff09; 文章目录 MySQL笔记&#xff08;四&#xff09;函数文本处理函数日期和时间处理函数数值处理函数类型转换函数流程控制函数自定义函数基本语法 局部变量全局变量聚集函数 aggregate functionDISTINCT 存储过程为什么要使用使用创建 删除建…

调用api实现ChatGPT接口余额查询

先打个广告&#xff1a; 推荐一款不用科学上网就可以使用的ChatGPT工具&#xff1a;智能聊天助手 体验版入口&#xff1a;智能聊天助手体验版 在ChatGPT官网可以查询接口使用额度&#xff0c;但是官方并没有提供相应的API给开发者调用。但是可以通过破解的方式找到它的API。方法…

人生在世皆有过错,来一起看看Java中的异常吧!!!

Java中的异常问题详解 一、异常的概念与分类 1.异常概念 概念&#xff1a;Java异常是一个描述在代码段中发生异常的对象&#xff0c;当发生异常情况时&#xff0c;一个代表该异常的对象被创建并且在导致该异常的方法中被抛出&#xff0c;而该方法可以选择自己处理异常或者传…

Invicti v23.5 for Windows 发布 - 企业应用安全测试

Invicti v23.5 for Windows - 企业应用安全测试 Invicti Standard 11 May 2023 v23.5.0.40516 请访问原文链接&#xff1a;https://sysin.org/blog/invicti/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Invicti 是一种自动…

[HFCTF2022]ezchain

环境分析 环境提供了docker-compose.yml&#xff0c;nginx.conf文件&#xff0c;从两个文件中可疑分析出是不出网的环境 nginx.conf&#xff1a; server { listen 80;server_name localhost;location / {root /usr/share/nginx/html; #收到/路径请求会访问/usr/sha…

Keil5----Debug时,watch1中全局变量数值不刷新问题解决方法

问题&#xff1a; 在Keil5-MDK中&#xff0c;Debug时&#xff0c;watch1中全局变量数值不刷新。 解决方法&#xff1a; 步骤1&#xff1a;进入Debug模式 将程序调试下载器&#xff08;STlink,Jlink,Ulink&#xff09;连接&#xff0c;编译程序后。 进行如下操作&#xff1a…