链表(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;这都是 花…

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 …

【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;代码如…

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;这是一款拥有后端的闯关娱乐小程序。 支持个人小程序和企业小程序上线运营 功能强大齐全,带数据本地化 (数据在自己服务器自己管理无需担心第三方失效的问题) 支持看视…

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

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

Reactor网络模式

文章目录 1. 关于Reactor模式的了解2. 基于Reactor模式实现epoll ET服务器2.1 EventItem类的实现2.2 Reactor类的实现Dispatcher函数AddEvent函数DelEvent函数EnableReadWrite函数 2.3 四个回调函数的实现acceptor回调函数recver回调函数sender回调函数errorer回调函数 3. epol…

突破封锁|华为芯片10年进化史:从K3V1到麒麟9000S

华为海思麒麟芯片过去10年研发历程回顾如下&#xff1a; 2009年&#xff1a;华为推出第一款手机芯片K3V1&#xff0c;采用65nm工艺制程&#xff0c;基于ARM11架构&#xff0c;主频600MHz&#xff0c;支持WCDMA/GSM双模网络。这款芯片搭载在华为U8800手机上&#xff0c;标志着华…

在SOLIDWORKS搭建一个简易的履带式机器人

文章目录 前言一、构建模型基本单元二、搭建车体模块三.插入轮子4.构建履带 前言 趁着十一假期&#xff0c;在solidworks中搭建了一个履带式机器人小车&#xff0c;计划将其应用在gazebo中完成多机器人编队的仿真。 一、构建模型基本单元 构建底板&#xff08;a面&#xff09…

面试总结-Redis篇章(十二)——Redis是单线程的,为什么还那么快

Redis是单线程的&#xff0c;为什么还那么快 Redis是单线程的&#xff0c;为什么还那么快什么是IO多路复用 阻塞IO非阻塞IOIO多路复用 Redis是单线程的&#xff0c;为什么还那么快 Redis是纯内存操作&#xff0c;执行速度非常快采用单线程&#xff0c;避免不必要的上下文切换可…

Python中套接字实现服务端和客户端3-3

3 创建客户端的步骤 创建客户端的步骤如图5所示。 图5 创建客户端的步骤 从图5可以看出&#xff0c;对于客户端来说&#xff0c;首先创建套接字&#xff0c;之后通过创建的套接字去连接服务端&#xff0c;如果连接成功&#xff0c;则继续通过该套接字向服务端发送数据&#x…

【Mybatis源码】IDEA中Mybatis源码环境搭建

一、Mybatis源码源 在github中找到Mybatis源码地址&#xff1a;https://github.com/mybatis/mybatis-3 找到Mybatis git地址 二、IDEA导入Mybatis源码 点击Clone下载Mybatis源码 三、选择Mybatis分支 选择Mybatis分支&#xff0c;这里我选择的是3.4.x分支

【ChatGPT】无需代理使用ChatGPT

推荐一个无需代理、可以直接使用的、免费的、无需客户端的、稳定的ChatGPT终端 支持GPT-3.5和CPT-4 无需境外手机号 该工具比较稳定&#xff0c;断流情况很少 GPTDOS 注册地址&#xff1a;GPTDOS &#xff08;使用我的邀请链接进行注册&#xff0c;双方都可以得到50000个toke…

Vue3 编译原理

文章目录 一、编译流程1. 解读入口文件 packgages/vue/index.ts2. compile函数的运行流程 二、AST 解析器1. ast 的生成2. 创建ast的根节点3. 解析子节点 parseChildren&#xff08;关键&#xff09;4. 解析模版元素 Element模版元素解析-举例分析 一、编译流程 1. 解读入口文…