链表(2)——带头双向循环链表

🍁一、链表的分类

🌕1.单向或者双向

🌕2.带头或者不带头(有无哨兵)

🌕3.循环或者不循环

🌕4.无头单向非循环链表(常用)

🌕5.带头双向循环链表(常用)

🌕注意:

1. 无头单向非循环链表: 结构简单 ,一般不会单独用来存数据。实际中更多是作为 其他数据结 构的子结构 ,如哈希桶、图的邻接表等等。另外这种结构在 笔试面试 中出现很多。
2. 带头双向循环链表: 结构最复杂 ,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

🍁二、双向链表的定义:

我们知道单链表的结点有一个数据域用于存放数据,一个指针域用于指向下一个结点。而 双向链表即是在此基础上每个结点多了一个指针域用于指向前一个结点;

🍁三、带头双向循环链表的定义

带头双向循环链表:即在双向链表的基础上,尾结点的next域指向头结点,使之体现出一个循环的结构。


🍁四、带头双向循环链表操作实现(多文件)

🌕1.定义:

只需在单链表定义的基础上多一个指针域prve,用于指向前驱;

typedef int SLDataType;typedef struct ListNode
{struct ListNode* prev;//指向前驱struct ListNode* next;//指向后继SLDataType data;//数据域
}ListNode;

🌕2.获得新结点

因为后续经常用到此函数,所以首先介绍。

操作很简单,用malloc函数生成即可

//获得新结点
ListNode* BuyLTNode(SLDataType x)
{//用malloc函数动态生成即可ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){//检查malloc错误原因perror("malloc");exit(-1);}//处理新结点的成员node->data = x;node->next = NULL;node->prev = NULL;return node;
}

🌕3.初始化

①:原本初始化需要改变头结点phead,所以需要结构体二级指针,但其他操作都不需要二级指针,所以为了排面,我们可以用返回值来代替使用二级指针;

②:初始化只需要获得一个新结点作为一个头结点,然后头结点的两个指针域互相指向代表此时为空表;

//初始化
ListNode* Init()
{//获得头结点,头结点数据域可以存点有意义的数据,也可以随便存,因为用不着ListNode* phead = BuyLTNode(0);//初始化头结点的两个指针域指向头结点本身表示为空表phead->next = phead;phead->prev = phead;return phead;
}

🌕4.尾插法

该种类链表虽然结构复杂,但操作却非常简单,比如尾插法就有几点优势于单链表;

2.1:优势

①:单链表尾插需要考虑元素是否为空,当链表中没有元素时会改变头指针(头结点),所以需要使用结构体二级指针;但带头双向循环链表因为带有头,所以不管有无元素,在尾插时只需改变结构体指针域,即改变结构体,所以都只需要使用结构体指针;

②:单链表尾插时需要找到尾结点,但带头双向循环链表不需要,因为多了一个prev指针域,头结点的prev域就是尾结点;可以参考上述图片;

2.2:尾插法大致分为“四步骤”:

首先创建一个临时指针tail指向头结点的prev域,即指向尾结点便于操作

①:将tail的next域指向新结点;

②:将新结点的prev域指向tail结点(尾结点);

③:将新结点的next域指向头结点;

④:将头结点的prev域指向新结点。

2.3:源代码
//尾插
//因为带有头,所以操作只需要改变结构体,所以只需要结构体指针
//具体操作看注释
void LTPushBack(ListNode* phead, SLDataType x)
{//因为是带头的,所以phead至少是个头指针,所以phead不可能为空,所以需要用assert检查一下assert(phead);//找到尾结点tailListNode* tail = phead->prev;//获取新结点newnodeListNode* newnode = BuyLTNode(x);//四步骤tail->next = newnode;newnode->prev = tail;phead->prev = newnode;newnode->next = phead;
}

🌕5.打印数据

此链表打印数据与单链表有一个区别,就是结束条件不同;因为带头双向循环链表的尾结点的next域不指向NULL,而是指向头结点,所以结束条件为“tail==head”;

//打印
void LTprint(ListNode* phead)
{//创建一个临时指针便于遍历操作ListNode* node = phead->next;//为了体现此链表结构而打印printf("phead<->");//打印数据,当临时指针node等于头结点时结束while (node != phead){printf("%d<->", node->data);node = node->next;}//为了体现此链表结构而打印printf("phead\n");
}

🌕6.尾删法

6.1:相对于单链表,该链表也有几个优点:

①:尾删不用找尾结点以及倒数第二个结点,用prev域就可以找到;

②:当表中只有一个元素时,单链表需要改变结构体指针,所以需要单独分类;而此链表因为有带头结点和prev域,所以用正常尾删方法即可;

6.2:尾删步骤:

①:判断单链表是否为空(条件:phead->next=phead时即为空);

②:创建一个临时指针tail1用于保存尾结点,方便后续释放尾结点;

③:创建一个临时指针tail2用于保存尾结点的prev域(尾结点的前一个结点),方便进行尾删操作;

④:tail2的next域指向头结点:tail2->next=phead;

⑤:头结点的prev域指向tail2结点:phead->prev=tail2;

⑥:释放尾结点tail1。

6.3:源代码:

//尾删
void LTPopBack(ListNode* phead)
{assert(phead);//检查是否为空if (phead->next == phead){printf("此链表为空,尾删失败!\n");return;}//临时指针保存结点ListNode* tail1 = phead->prev;ListNode* tail2 = phead->prev->prev;//断开与尾结点的链接tail2->next = phead;phead->prev = tail2;//释放尾结点free(tail1);
}

🌕7.头插法

同上,因为prev的存在,所以不用考虑初始表是否为空表的情况;

7.1:四步骤:

①:新结点的next域指向head的next域(即指向插入前的首结点);

②:head的next域的prev域指向新结点(即插入前的首结点的prev域指向新结点);

③:新结点的prev域指向头结点head;

④:头结点head的next域指向新结点。

7.2:源代码
//头插
void LTPushFront(ListNode* phead, SLDataType x)
{assert(phead);//新结点ListNode* newnode = BuyLTNode(x);//四步骤newnode->next = phead->next;phead->next->prev = newnode;newnode->prev = phead;phead->next = newnode;
}

🌕8.头删法

头删法也很简单,只需考虑个个指针的链接即可;

8.1:步骤

①:创建临时指针first指向首结点,便于后续释放首结点;

②:创建临时指针second指向第二个结点,便于进行删除操作;

③:改变指针链接:

second->prev = phead;

phead->next = second;

④:释放首结点;

8.2:源代码
//头删
void LTPopFront(ListNode* phead)
{assert(phead);if (phead->next == phead){printf("链表为空,头删失败!\n");return;}//临时指针first指向首结点,便于后续释放首结点//临时指针second指向第二个结点,便于进行删除操作ListNode* first = phead->next;ListNode* second = first->next;//删除second->prev = phead;phead->next = second;//释放首结点free(first);
}

🌕9.在pos位置之前插入结点

其实很简单,只需要搞得指针域的链接顺序,防止指针丢失即可

9.1:源代码如下:
 
//在pos位置之前插入结点
ListNode* LTInsrt(ListNode* pos, SLDataType x)
{assert(pos);//新结点ListNode* newnode = BuyLTNode(x);//插入pos->prev->next = newnode;newnode->prev = pos->prev;newnode->next = pos;pos->prev = newnode;
}
9.2:有了这个算法后我们可以改进头插与尾插:

①:当pos==phead->next时,即为头插算法:
 

//头插
void LTPushFront(ListNode* phead, SLDataType x)
{assert(phead);//改进LTInsrt(phead->next, x);
}

②:当pos等于phead时,即为尾插算法:

//尾插
void LTPushBack(ListNode* phead, SLDataType x)
{//因为是带头的,所以phead至少是个头指针,所以phead不可能为空,所以需要用assert检查一下assert(phead);//改进LTInsrt(phead, x);
}

🌕10.删除pos位置的结点

10.1:步骤:

①:创建临时指针first保存pos前一个结点;

②:创建临时指针second保存pos后一个结点;

③:改变指针链接,删除pos结点:

first->next = second; 

second->prev = first;

④:释放pos结点。

10.2:源代码
//删除pos位置的结点
void LTErase(ListNode* pos)
{assert(pos);//临时指针ListNode* first = pos->prev;ListNode* second = pos->next;//删除first->next = second;second->prev = first;//释放posfree(pos);
}
10.3:有了这个算法,我们可以改进头删与尾删

①:当pos==phead->next时,即为头删算法:

//头删
void LTPopFront(ListNode* phead)
{assert(phead);//改进LTErase(phead->next);
}

②:当pos==phead->prev时,即为尾删算法:

//尾删
void LTPopBack(ListNode* phead)
{assert(phead);//改进LTErase(phead->prev);
}

🍁五、测试源代码

main.c

#include"List.h"void STTest1()
{ListNode* plist = NULL;plist = Init();//初始化//尾插LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);//打印LTprint(plist);
}void STTest2()
{ListNode* plist = NULL;plist = Init();//初始化//尾插LTPushBack(plist, 1);//打印LTprint(plist);//尾删LTPopBack(plist);//打印LTprint(plist);
}void STTest3()
{ListNode* plist = NULL;plist = Init();//初始化//头插LTPushFront(plist, 1);LTPushFront(plist, 2);//打印LTprint(plist);//头删LTPopFront(plist);//打印LTprint(plist);//头删LTPopFront(plist);//打印LTprint(plist);
}int main()
{//STTest1();//STTest2();STTest3();return 0;
}

List.c

#include"List.h"//获得新结点
ListNode* BuyLTNode(SLDataType x)
{//用malloc函数动态生成即可ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){//检查malloc错误原因perror("malloc");exit(-1);}//处理新结点的成员node->data = x;node->next = NULL;node->prev = NULL;return node;
}//初始化
ListNode* Init()
{//获得头结点,头结点数据域可以存点有意义的数据,也可以随便存,因为用不着ListNode* phead = BuyLTNode(0);//初始化头结点的两个指针域指向头结点本身表示为空表phead->next = phead;phead->prev = phead;return phead;
}//打印
void LTprint(ListNode* phead)
{assert(phead);//创建一个临时指针便于遍历操作ListNode* node = phead->next;//为了体现此链表结构而打印printf("phead<->");//打印数据,当临时指针node等于头结点时结束while (node != phead){printf("%d<->", node->data);node = node->next;}//为了体现此链表结构而打印printf("phead\n");
}//尾插
//因为带有头,所以操作只需要改变结构体,所以只需要结构体指针
//具体操作看注释
void LTPushBack(ListNode* phead, SLDataType x)
{//因为是带头的,所以phead至少是个头指针,所以phead不可能为空,所以需要用assert检查一下assert(phead);找到尾结点tail//ListNode* tail = phead->prev;获取新结点newnode//ListNode* newnode = BuyLTNode(x);四步骤//tail->next = newnode;//newnode->prev = tail;//phead->prev = newnode;//newnode->next = phead;//改进LTInsrt(phead, x);
}//尾删
void LTPopBack(ListNode* phead)
{assert(phead);检查是否为空//if (phead->next == phead)//{//	printf("此链表为空,尾删失败!\n");//	return;//}临时指针保存结点//ListNode* tail1 = phead->prev;//ListNode* tail2 = phead->prev->prev;断开与尾结点的链接//tail2->next = phead;//phead->prev = tail2;释放尾结点//free(tail1);//改进LTErase(phead->prev);
}//头插
void LTPushFront(ListNode* phead, SLDataType x)
{assert(phead);//新结点//ListNode* newnode = BuyLTNode(x);四步骤//newnode->next = phead->next;//phead->next->prev = newnode;//newnode->prev = phead;//phead->next = newnode;//改进LTInsrt(phead->next, x);
}//头删
void LTPopFront(ListNode* phead)
{assert(phead);/*if (phead->next == phead){printf("链表为空,头删失败!\n");return;}*/临时指针first指向首结点,便于后续释放首结点临时指针second指向第二个结点,便于进行删除操作//ListNode* first = phead->next;//ListNode* second = first->next;删除//second->prev = phead;//phead->next = second;释放首结点//free(first);//改进LTErase(phead->next);
}//在pos位置之前插入结点
void LTInsrt(ListNode* pos, SLDataType x)
{assert(pos);//新结点ListNode* newnode = BuyLTNode(x);//插入pos->prev->next = newnode;newnode->prev = pos->prev;newnode->next = pos;pos->prev = newnode;
}//删除pos位置的结点
void LTErase(ListNode* pos)
{assert(pos);//临时指针ListNode* first = pos->prev;ListNode* second = pos->next;//删除first->next = second;second->prev = first;//释放posfree(pos);
}

List.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLDataType;typedef struct ListNode
{struct ListNode* prev;//指向前驱struct ListNode* next;//指向后继SLDataType data;//数据域
}ListNode;//获得一个新结点
ListNode* BuyLTNode(SLDataType x);//初始化
ListNode* Init();//打印
void LTprint(ListNode* phead);//尾插
void LTPushBack(ListNode* phead,SLDataType x);//尾删
void LTPopBack(ListNode* phead);//头插
void LTPushFront(ListNode* phead, SLDataType x);//头删
void LTPopFront(ListNode* phead);//在pos位置之前插入结点
void LTInsrt(ListNode* pos, SLDataType x);//删除pos位置的结点
void LTErase(ListNode* pos);

本次知识到此结束,希望对你有所帮助!

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

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

相关文章

案例分享:原生广告如何助力app实现高效变现收益的转化

原生广告是指将广告嵌入到APP的内容中&#xff0c;使其与APP内容融为一体&#xff0c;达到获得用户关注的效果。在形式上&#xff0c;原生广告并不像传统广告那样显眼&#xff0c;而是以一种更加自然的方式展现在用户面前。 它采用了与APP相似的设计风格和交互方式&#xff0c…

深度学习DAY1:神经网络NN;二元分类

深度学习笔记 DAY1 深度学习基本知识 1.神经网络 1.1 单一神经元 所有神经元将房屋大小size作为输入x,计算线性方程&#xff0c;结果取max&#xff08;0&#xff0c;y&#xff09;,输出预测房价y ReLU函数&#xff08;线性整流函数&#xff09;–max&#xff08;0&#xf…

Axios、SASS学习笔记

目录 前言 一、Axios基础认识 1、简介 2、相关文档 3、基本配置 4、基础快捷使用 二、Axios封装 1、公共配置文件 2、细化每个接口的配置 3、使用并发送请求 三、SASS 1、简介 2、相关文档 3、使用前奏 4、使用变量 5、嵌套规则 6、父级选择器标识 & 前言…

Linux基本指令(下)——“Linux”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容仍然是Linux中的基本指令啦&#xff0c;下面&#xff0c;让我们进入Linux的世界吧&#xff01;&#xff01;&#xff01; Cal指令 find指令&#xff1a;&#xff08;灰常重要&#xff09; -name grep指令 zip/un…

【置顶】关于博客的一些公告

所谓 万事开头难&#xff0c;最开始的两个专栏 《微机》 和 《骨骼动作识别》 定价 29.9 &#xff0c;因为&#xff1a; 刚开始确实比较困难&#xff0c;要把自己学的知识彻底搞懂讲给别人&#xff0c;还要 码字排版&#xff0c;从 Markdown 语法开始学起&#xff08;这都是 花…

机器学习基础-手写数字识别

手写数字识别&#xff0c;计算机视觉领域的Hello World利用MNIST数据集&#xff0c;55000训练集&#xff0c;5000验证集。Pytorch实现神经网络手写数字识别感知机与神经元、权重和偏置、神经网络、输入层、隐藏层、输出层mac gpu的使用本节就是对Pytorch可以做的事情有个直观的…

leetcode 139. 单词拆分

39. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#xff1a; 输入: s "leetcode"…

java学习--day24(stream流)

文章目录 今天的内容1.Stream【难点】1.1获取流的对象1.2Stream流对象下面1.2.1count和forEach1.2.2filter方法1.2.3limit1.2.4map方法1.2.5skip1.2.6concat 1.3收集流 1.基于接口和抽象类的匿名内部类的写法 abstract class Person {public abstract void eat(); } public sta…

Pytorch因nn.Parameter导致实验不可复现的一种情况

文章首发见博客&#xff1a;https://mwhls.top/4871.html。 无图/格式错误/后续更新请见首发页。 更多更新请到mwhls.top查看 欢迎留言提问或批评建议&#xff0c;私信不回。 没解决&#xff0c;只是记录这种情况。 也可以多次实验取均值以避免结果复现。 场景 自己的模块中&a…

Hadoop2.0探讨

文章目录 8. Hadoop 再探讨8.1 Hadoop的优化与发展8.2 HDFS 的FA和Federation(Hadoop2.0新特性)8.2.1 HDFS HA8.2.2 HDFS Federation 8.3 YARN8.3.1 MapReduce1.0的缺陷8.3.2 Yarn设计思路8.3.3 Yarn体系结构8.3.4 Yarn工作流程8.3.5 Yarn框架和MapReduce1.0框架对比分析8.3.6 …

#力扣:206. 反转链表@FDDLC

206. 反转链表 一、Java class Solution {public ListNode reverseList(ListNode head) {if (head null) return null;ListNode pre null, cur head, next head.next;while (next ! null) {cur.next pre;pre cur;cur next;next cur.next;}cur.next pre;return cur;}…

〖程序员的自我修养 - 认知剖析篇⑤〗- 选择前端还是后端?

人之所以会觉得迷茫,本质上是欠缺对自己的一个控制力、识别庞杂信息、去伪存真的独立思考与认知能力。 说明:该文属于 程序员的自我修养 专栏,购买任意白宝书体系化专栏可加入易编程社区,早鸟价订阅模式除外。福利:加入社区的小伙伴们,除了可以获取博主所有付费专栏的阅读…

【Java 进阶篇】CSS 选择器详解

CSS&#xff08;层叠样式表&#xff09;是一种用于描述网页上元素样式的语言。要想有效地使用CSS&#xff0c;了解CSS选择器是至关重要的&#xff0c;因为它们允许你选择要应用样式的HTML元素。在本文中&#xff0c;我们将详细介绍CSS选择器的各种类型和用法&#xff0c;以便你…

Vue中使用Echarts封装为公用组件(简单复制粘贴)

Vue中封装Echarts组件 前提直奔主题 本文以Vue3代码演示 Vue2同理 前提 中文官网&#xff1a; https://echarts.apache.org/zh/index.html npm安装Echarts npm install echarts or pnpm install echarts or yarn add echarts直奔主题 创建Echarts.vue文件&#xff0c;代码如…

2_dataset, dataloader

dataset, dataloader torchvision.datasets里面集成了一些常见的数据集,例如MNIST和CIFAR10 1) Dataset 以MNIST为例,其使用方式如下 import torch import torchvision from torchvision import transformstrain_dataset = torchvision.datasets.MNIST(root=../data,trai…

Solidity 合约漏洞,价值 38BNB 漏洞分析

Solidity 合约漏洞&#xff0c;价值 38BNB 漏洞分析 1. 漏洞简介 https://twitter.com/NumenAlert/status/1626447469361102850 https://twitter.com/bbbb/status/1626392605264351235 2. 相关地址或交易 攻击交易&#xff1a; https://bscscan.com/tx/0x146586f05a451313…

Unity官方文档中关于内存管理的翻译(2021.3)

原文:Memory in Unity - Unity 手册 Unity内存管理 为了确保您的应用程序运行时没有性能问题&#xff0c;了解Unity如何使用和分配内存非常重要。本文档的这一部分解释了Unity中内存是如何工作的&#xff0c;适用于希望了解如何提高应用程序内存性能的读者。 Unity使用三个内…

视频答题猜歌闯关娱乐微信小程序源码支持看视频答题闯关听歌猜歌答题流量主模式(团队奖励等)

功能强大UI美观的视频答题猜歌闯关娱乐微信小程序源码下载 后台管理资源本地化带数据和视频教程&#xff0c;这是一款拥有后端的闯关娱乐小程序。 支持个人小程序和企业小程序上线运营 功能强大齐全,带数据本地化 (数据在自己服务器自己管理无需担心第三方失效的问题) 支持看视…

深度学习常用脚本总结

&#x1f468;‍&#x1f4bb;个人简介&#xff1a; 深度学习图像领域工作者 &#x1f389;工作总结链接&#xff1a;https://blog.csdn.net/qq_28949847/article/details/128552785 链接中主要是个人工作的总结&#xff0c;每个链接都是一些常用demo&#xff0c…

【重拾C语言】七、指针(二)指针与数组(用指针标识数组、多维数组与指针、数组指针与指针数组)

目录 前言 七、指针 7.1~3 指针与变量、指针操作、指向指针的指针 7.4 指针与数组 7.4.1 用指针标识数组 7.4.2 应注意的问题 a. 数组名是指针常量 b. 指针变量的当前值 c. 数组超界 7.4.3 多维数组与指针 7.4.4 指针数组 a. 指针数组 b. 数组指针 c. 对比总结 前…