【初阶数据结构】带头双向循环链表讲解

前言 

📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。

📚本文收录与初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!

📚相关专栏C++及Linux正在发展,敬请期待!

目录

前言 

1. 带头双向循环链表的实现

1.1  概念

1.2 链表的实现

1.2.1 链表的基本搭建 

1.2.2 结点动态内存开辟的函数 

1.2.3 链表的初始化 

1.2.4 链表的打印 

1.2.5 链表的尾插 

1.2.6 链表的头插

1.2.7 链表的尾删

1.2.8 链表的头删

1.2.9 链表的查找 

1.2.10 链表的任意位置前插

1.2.11 链表在指定位置的删除 

1.2.12 释放链表 

2. 完整代码 

2.1 List.c

2.2 List.h

2.3 test.c  

总结


1. 带头双向循环链表的实现

1.1  概念

之前我给大家讲过不带头单向不循环链表,这次我再给大家介绍一个也同样很重要的,叫做带头双向循环链表。

带头双向循环链表,顾名思义,就是带哨兵位,有前后指针,有循环的链表。 

结构如下:

1.2 链表的实现

1.2.1 链表的基本搭建 

typedef int LTtypeData;typedef struct LTNode
{struct LTNode* prev;struct LTNode* next;LTtypeData data;}LTNode;

首先,双向表示需要两个指针,一个是prev指针,可以找到前一个结点,一个是next指针,可以找到下一个结点,还有一个data来存放数据。

1.2.2 结点动态内存开辟的函数 

这个地方很重要,因为后面基本都是要通过这个函数来申请结点, 

LTNode* BuyLTNode(LTtypeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

1.2.3 链表的初始化 

链表的初始化其实很简单,申请一个结点,然后让结点自己指向自己,构成循环。看下图

代码实现:

LTNode* LTcreate()
{LTNode* phead = BuyLTNode(-1);assert(phead);phead->prev = phead;phead->next = phead;return phead;
}

为什么要返回结构体指针?因为外面结构体指针想改变,是不是要么就是传外面结构体的地址,要么就是带返回值,很明显后者更简单。

1.2.4 链表的打印 

这个带循环的链表就不是到空指针就结束这么判断了,而是不等于头结点就继续,等于是不是代表已经循环完一圈了,就结束。

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

1.2.5 链表的尾插 

尾插需不需要像单链表一样找尾, 是不是不需要啊?哪里是尾?是不是phead->prev是尾,看图

代码实现:

void LTPushBack(LTNode* phead, LTtypeData x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* tail = phead->prev;newnode->prev = tail;tail->next = newnode;phead->prev = newnode;newnode->next = phead;
}

首先我们先创建一个newnode新节点,要尾插的结点,然后将尾部和新结点相连接是不是就可以了,在把新结点和头结点相连接是不是就尾插成功了?

1.2.6 链表的头插

头插怎么做?是不是需要找到phead,和记录phead的next,也就是下一个,然后让newnode结点和phead与next相连接就ok。

void LTPushFront(LTNode* phead, LTtypeData x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* next = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = next;next->prev = newnode;
}

 1.2.7 链表的尾删

删除要特别注意,链表为空就不要删了。还需不需要像单链表一样找尾,再找尾的前一个?不需要,看图:

代码实现: 

void LTPopBack(LTNode* phead)
{assert(phead);//没有再删是要出问题的assert(!IsEmpty(phead));LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);phead->prev = tailPrev;tailPrev->next = phead;
}

在这里介绍一下这个bool函数,这个可以表示是真还是假,那么函数设置

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

这个代码的意思是,如果返回值是真,说明一件事,就是确实是空链表,那assert断言的时候加一个逻辑非即可表示false,便于我们判断是不是空链表。 

1.2.8 链表的头删

这个就很简单了,找到链表的下一个和下下一个,让链表指向下下一个结点即可,看图:

 代码实现:

void LTPopFront(LTNode* phead)
{//方法一 一个指针assert(phead);assert(!IsEmpty(phead));LTNode* next = phead->next;phead->next = next->next;next->next->prev = phead;
}

1.2.9 链表的查找 

这个就更加的简单了,输入要找的值,返回这个地方的结构体指针就ok

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

1.2.10 链表的任意位置前插

 

在pos前插,是不是需要找到pos前面的位置,互相链接即可。

void ListInsert(LTNode* pos, LTtypeData x)
{assert(pos);LTNode* posprev = pos->prev;LTNode* newnode = BuyLTNode(x);posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}

那么我就问大家两个问题,如何用任意位置的前插来表示头插和尾插?

首先来讲下头插:

头插是不是传phead->next是不是就可以啊,为什么? 

这个是标准的头插。

2、尾插传什么参数?

尾插是不是要找尾,谁的prev是尾,是不是phead,是不是传phead就可以了?是的。

1.2.11 链表在指定位置的删除 

是不是很简单,为什么?因为找指定位置的前一个和后一个,最后链接起来是不是就可以了? 

void ListErase(LTNode* pos)
{assert(pos);assert(!IsEmpty(pos));LTNode* posprev = pos->prev;LTNode* posnext = pos->next;free(pos);posprev->next = posnext;posnext->prev = posprev;
}

调用这个函数,如何实现尾删和头删?

尾删:是不是传phead->prev就可以?是的

头删:是不是传phead->next就可以 ?是的

1.2.12 释放链表 

 这个就是和单链表相同,一前一后指针即可。

void Destory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

2. 完整代码 

2.1 List.c

#define  _CRT_SECURE_NO_WARNINGS 1#include"List.h"LTNode* BuyLTNode(LTtypeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}bool IsEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}LTNode* LTcreate()
{LTNode* phead = BuyLTNode(-1);assert(phead);phead->prev = phead;phead->next = phead;return phead;
}void LTPushBack(LTNode* phead, LTtypeData x)
{assert(phead);/*LTNode* newnode = BuyLTNode(x);LTNode* tail = phead->prev;newnode->prev = tail;tail->next = newnode;phead->prev = newnode;newnode->next = phead;*/ListInsert(phead, x);
}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 LTPushFront(LTNode* phead, LTtypeData x)
{assert(phead);/*LTNode* newnode = BuyLTNode(x);LTNode* next = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = next;next->prev = newnode;*/ListInsert(phead->next, x);}void LTPopBack(LTNode* phead)
{assert(phead);//没有再删是要出问题的assert(!IsEmpty(phead));/*LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);phead->prev = tailPrev;tailPrev->next = phead;*/ListErase(phead->prev);
}void LTPopFront(LTNode* phead)
{//方法一 一个指针assert(phead);assert(!IsEmpty(phead));/*LTNode* next = phead->next;phead->next = next->next;next->next->prev = phead;*/ListErase(phead->next);
}LTNode* LTFind(LTNode* phead, LTtypeData x)
{assert(phead);assert(!IsEmpty(phead));LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}void ListInsert(LTNode* pos, LTtypeData x)
{assert(pos);LTNode* posprev = pos->prev;LTNode* newnode = BuyLTNode(x);posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}void ListErase(LTNode* pos)
{assert(pos);assert(!IsEmpty(pos));LTNode* posprev = pos->prev;LTNode* posnext = pos->next;free(pos);posprev->next = posnext;posnext->prev = posprev;
}void Destory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

2.2 List.h

#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int LTtypeData;typedef struct LTNode
{struct LTNode* prev;struct LTNode* next;LTtypeData data;}LTNode;//创建新结点
LTNode* LTcreate();//判断是否没有数据
bool IsEmpty(LTNode* phead);
//开始插入数据了
//尾插
void LTPushBack(LTNode* phead, LTtypeData x);//头插
void LTPushFront(LTNode* phead, LTtypeData x);//尾删
void LTPopBack(LTNode* phead);//头删
void LTPopFront(LTNode* phead);
//打印数据
void LTPrint(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTtypeData x);//在pos前面进行插入
void ListInsert(LTNode* pos, LTtypeData x);//删除pos位置的值void ListErase(LTNode* pos);//销毁链表
void Destory(LTNode* phead);

2.3 test.c  

这个就留给大家自己去测试函数了。


总结

1、大家一定要自己来写一下带头双向循环链表,因为真的很实用而且很简单

2、下一节给大家更新栈和队列

 如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言

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

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

相关文章

开源连锁收银系统哪个好

针对开源连锁收银系统的选择&#xff0c;商淘云是一个备受关注的候选。商淘云以其功能丰富、易于定制和稳定性等优势&#xff0c;吸引了众多企业和开发者的关注。下面将从四个方面探讨商淘云开源连锁收银系统的优势&#xff1a; 首先&#xff0c;商淘云提供了丰富的功能模块。作…

报错:(idea端口被占用)Web server failed to start. Port 9090 was already in use.

cmd里面输入&#xff1a; netstat -ano|findstr "9090" 可以看到pid是9644 然后再打开任务管理器

卷轴分红商城模式:适用于多种的商业营销模式

卷轴分红商城模式是一种基于区块链技术的去中心化积分商城系统&#xff0c;通过智能合约和数字资产分红实现积分流通和价值回馈&#xff0c;适用于多种场景。 什么是卷轴分红商城模式&#xff1a; 这是一个去中心化的积分商城系统&#xff0c;消费者在商城消费时&#xff0c;可…

GDPU 竞赛技能实践 天码行空 期末小测

1. 除法&#xff08;原题&#xff09; &#x1f468;‍&#x1f3eb; 实验二&#xff1a;1.简单枚举 输入正整数n&#xff0c;按从小到大的顺序输出所有形如abcde/fghij n的表达式&#xff0c;其中a&#xff5e;j恰好为数字0&#xff5e;9的一个排列&#xff08;可以有前导0&a…

终于搞懂Linux 设备树中的#address-cells,#size-cells 和reg 属性

目录 一、前置知识 1. 处理器平台2. reg 属性的基本格式3. reg 属性的作用 reg 用法 二、#address-cells 和 #size-cells 属性 1. 示例1 2. 示例23. 示例3 一、前置知识 要理解#address-cells和#size-cell 这两个属性&#xff0c;就要先了解 reg属性。 1. 处理器平台 下…

上班族兼职新篇章:10大实战攻略,轻松年赚1-20万

对于众多上班族而言&#xff0c;如何在工作之余赚取额外收入&#xff0c;开启自己的第一份副业&#xff0c;已成为许多人心中的疑问。每个人的才能和兴趣点不尽相同&#xff0c;但都有机会找到适合自己的兼职方式。接下来&#xff0c;就让我们一起探索这10大实战攻略&#xff0…

Zynq UltraScale+ MPSoC 配置存储器器件

Zynq UltraScale MPSoC 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 Zynq UltraScale MPSoC 器件执行擦除、空白检查、编程和验证等配置操 作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列…

C++错题集(持续更新ing)

Day 1 一、选择题 解析&#xff1a; 在数字不会溢出的前提下&#xff0c;对于正数和负数&#xff0c;有&#xff1a; 1&#xff09;左移n位&#xff0c;相当于操作数乘以2的n次方&#xff1b; 2&#xff09;右移n位&#xff0c;相当于操作数除以2的n次方。 解析&#xff1a…

汇聚荣科技:拼多多上架商品后需要做页面推广吗?

在电商平台上&#xff0c;商品的曝光率和销量往往成正比。那么&#xff0c;当您在拼多多上架了新品&#xff0c;是不是就意味着坐等订单呢?答案显然是否定的。商品一旦上架&#xff0c;接下来需要做的就是通过有效的页面推广来增加商品的可见度&#xff0c;吸引潜在买家的注意…

熬了快两个月,终于拿到了淘天后端offer!

今年的暑期实习挺难找的&#xff0c;很多同学忙了几个月到现在还没有一个offer&#xff0c;真的很常见&#xff01;没找到暑期实习的同学千万不要太焦虑&#xff0c;可以留意留意日常实习&#xff0c;日常实习也找不到&#xff0c;那就去完善自己的项目经历&#xff0c;认真准备…

FreeRTOS【6】线程优先级

1.开发背景 基于上一篇指引&#xff0c;已经了解了线程的阻塞&#xff0c;这个篇章主要介绍线程优先级的影响 2.开发需求 设计实验验证高优先级会抢占低优先级线程 CPU 3.开发环境 window10 MDK STM32F429 FreeRTOS10.3.1 4.实现步骤 1&#xff09;创建测试线程&#xff…

详解绝对路径和相对路径的区别

绝对路径和相对路径是用于描述文件或目录在文件系统中位置的两种不同方式。 绝对路径&#xff08;Absolute Path&#xff09;是从文件系统的根目录开始的完整路径&#xff0c;可以唯一地确定一个文件或目录的位置。在不同的操作系统中&#xff0c;根目录的表示方式可能略有不同…