带头循环双向链表详解

目录

一、什么是带头循环双向链表?

1.特点:

2.优点:

二、实现接口

1.前置准备

1.1需要的三个文件

1.2结构体的创建和头文件的引用

2.接口实现

2.1函数创建新节点

2.2打印链表内容

 2.3尾插新节点

2.4头插新节点

 2.5头删节点

2.6尾删节点

 2.7查找节点

2.8在指定位置前插入节点

2.9删除指定位置节点.

2.10摧毁链表

 三、全部代码

1.接口头文件

2.接口实现

3.测试文件


一、什么是带头循环双向链表?

1.特点:

1.带头:有哨兵位节点,它不用存储数据。对链表进行插入删除操作时也不会影响改节点。

2.双向:组成链表的结构体中的结构体成员有数据,上一个节点的地址和下一个节点的地址

3.循环:链表的头结点存储了尾结点的地址,链表的尾结点存储了头节点的地址。

2.优点:

相比单向链表,双向循环链表的优点在于它的尾插找尾巴非常的快    因为它的头节点同时存储了上一个节点的地址,头的上一个即尾。相比无头链表,带头链表的好处在于当没有节点的时候,可以直接通过访问结构体成员的方式来增加相应的指针,而无头的话需要直接对地址进行修改,传变量的时候还需要传递二级指针   不仅不好理解,还易在实现的时候出错。

二、实现接口

1.前置准备

1.1需要的三个文件

先创建两个.c文件,再创建一个头文件,分别命名为test.c,list.c,list.h

test.c用来测试写好的接口                                   list.c存放实现接口的代码

list.h则存放对应函数,头文件,结构体的声明,这样在想使用链表的接口时,直接引用list.h即可,不需要引用别的头文件。 

创建好的环境如图

1.2结构体的创建和头文件的引用

这些内容放在list.h的文件中,到时引用就可以一条龙带走,不需要再引用别的内容

#pragma once//防止头文件二次引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDateType;
//这样创建结构体数据类型,不仅是为了和int做区分
//也是为了到时更好的替换,想换直接把int改了就行
typedef struct listnode
{struct listnode* prev;//存放上一个节点的地址struct listnode* next;//存放下一个节点的地址LTDateType data;//该节点存放的数据
}listnode;

2.接口实现

2.1函数创建新节点

创建节点,虽然简单,但我们在很多操作中都会用到,因此把它单独分装成一个接口

listnode* buy_listnode(LTDateType x)
{listnode*newnode=(listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("buy_listnode");//错误提示exit(-1);//节点都没创建出来,直接退出程序}newnode->data = x;//将新节点的数据初始化成我们需要的newnode->next = NULL;//不清楚插入的方式,先初始化成空newnode->prev = NULL;
}

2.2打印链表内容

非常经典的操作,遍历一遍链表即可,唯一需要注意的便是,哨兵节点不是链表中真正的成员,它只能算是实现链表的辅助,因此跳过哨兵节点进行打印

void print_list(listnode* phead)
{assert(phead);//哨兵节点地址不可能为空listnode* head = phead->next;//哨兵位节点不存储有效数据,因此phead->next才是头节点printf("head<=>");//纯属美观while (head != phead)//当head和phead相等意味着已经遍历完一遍链表{printf("%d<=>", head->data);head = head->next;}printf("\n");
}

 2.3尾插新节点

void list_pushback(listnode*phead,LTDateType x)
//尾插一个新节点,此节点存储x
{listnode* newnode = buy_listnode(x);//创建一个我们需要的新节点listnode* tail = phead->prev;//先找尾,尾很显然是哨兵位节点的上一个节点tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

后面的4行代码是核心,单独在文章中解释,创建了一个新节点,要把它放到链表的末端,尾节点我们已经找到了,接下来就是链接即可

首先明确,新的尾巴是创建出来的新节点,但还没进行链接之前,尾巴还是之前的尾巴

原始链表

第一步: 

第二步: 

 

第三步:

 第四步:

测试代码:

#include"list.h"
void test1()
{listnode* plist=NULL;plist=init_listnode(plist);list_pushback(plist,1);list_pushback(plist,2);list_pushback(plist,3);list_pushback(plist,4);print_list(plist);
}
int main()
{test1();
}

 测试效果:

2.4头插新节点

这里我就不再画图了,自己画一遍比看别人画一万遍都来的快 

void list_pushfront(listnode* phead, LTDateType x)
{listnode* head = phead->next;//找到头节点listnode* newnode = buy_listnode(x);//创建新节点head->prev = newnode;newnode->next = head;phead->next = newnode;newnode->prev = phead;
}

测试代码:

void test2()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);print_list(plist);list_pushfront(plist, 10086);print_list(plist);
}
int main()
{test2();
}

测试效果: 

 2.5头删节点

需要注意的一点便是,我们删的节点不是哨兵节点,哨兵节点是不存放有效数据的,我们删除的是头节点

void list_popfront(listnode*phead)
{assert(phead);if (phead->next == phead){printf("链表为空,操作失败\n");//为空就别删了return;}listnode* head = phead->next;//找到头节点phead->next = head->next;head->next->prev = phead;free(head);//链接完成,彻底删除
}

测试代码:

void test3()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);print_list(plist);list_pushfront(plist, 10086);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);
}
int main()
{test3();
}

测试效果:

 

2.6尾删节点

没什么好说的,和之前的一样关键点在链接上,自己画了图什么都知道

void list_popback(listnode*phead)
{assert(phead);if (phead->next == phead){printf("链表为空,操作失败\n");//为空就别删了return;}listnode* tail = phead->prev;//找到尾节点phead->prev = tail->prev;tail->prev->next = phead;free(tail);
}

测试代码:

void test4()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 10086);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);
}
int main()
{test4();
}

测试效果: 

 2.7查找节点

遍历一遍,找不到就返回NULL即可

listnode* list_find(listnode* phead, LTDateType x)
//哨兵节点和目标
{assert(phead);listnode* head = phead->next;//找到头节点while (head!=phead)//相等意味着已经遍历完了{if (head->data == x)//找到目标,直接返回{return head;}head = head->next;}return NULL;//遍历完还找不到,返回空指针
}

2.8在指定位置前插入节点

根据目标进行链接即可

void list_insert(listnode*pos,LTDateType x)
//目标位置,和在其前面插入数据为x的节点
{if (pos == NULL)//传空意味着没找到目标{printf("目标不存在,操作失败\n");return;}listnode*newnode=buy_listnode(x);//创建新节点newnode->next = pos;newnode->prev= pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}

测试代码:

void test5()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 10086);print_list(plist);listnode*pos=list_find(plist,2);list_insert(pos, 888);//在2之前插入888print_list(plist);list_insert(plist->next, 666);//在头节点前插入666,与头插效用一致//可以在头插中复用这个函数print_list(plist);list_insert(plist, 520);//在哨兵节点前插入520,与尾插效用一致//可以在尾插中复用这个函数print_list(plist);}
int main()
{test5();
}

测试效果:

2.9删除指定位置节点.

void list_erase(listnode* pos)
{assert(pos && pos->next != pos);//pos为空意味着不存在,pos->next==pos意味着为哨兵节点pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);
}

测试代码:

void test6()
{listnode* plist = NULL;plist = init_listnode(plist);//list_erase(plist->prev);//尾删,测试报错list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 3);list_pushfront(plist, 4);list_pushfront(plist, 5);print_list(plist);listnode* pos = list_find(plist, 2);list_erase(pos);//把2删除print_list(plist);list_erase(plist->next);//头删print_list(plist);list_erase(plist->prev);//尾删print_list(plist);
}
int main()
{test6();
}

测试效果:

2.10摧毁链表

void destory_list(listnode* phead)
{listnode* tail = phead->prev;while (tail != phead){listnode* tmp = tail;//存储尾tail = tail->prev;//从后往前遍历free(tmp);//不需要管什么链接了,直接摧毁就行}free(phead);//单独摧毁
}

 测试代码:
 

void test7()
{listnode* plist = NULL;plist = init_listnode(plist);//list_erase(plist->prev);//尾删,测试报错list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 3);list_pushfront(plist, 4);list_pushfront(plist, 5);destory_list(plist);
}
int main()
{test7();
}

测试效果:

从监视来看,确实全部释放

 三、全部代码

1.接口头文件

#pragma once//防止头文件二次引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDateType;
//这样创建结构体数据类型,不仅是为了和int做区分
//也是为了到时更好的替换,想换直接把int改了就行
typedef struct listnode
{struct listnode* prev;//存放上一个节点的地址struct listnode* next;//存放下一个节点的地址LTDateType data;//该节点存放的数据
}listnode;
listnode* buy_listnode(LTDateType x);
listnode* init_listnode(listnode* phead);
void print_list(listnode* phead);
void list_pushback(listnode* phead, LTDateType x);
void list_pushfront(listnode* phead, LTDateType x);
void list_popfront(listnode* phead);
void list_popback(listnode* phead);
listnode* list_find(listnode* phead, LTDateType x);
void list_insert(listnode* pos, LTDateType x);
void list_erase(listnode* pos);
void destory_list(listnode* phead);

2.接口实现

#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
listnode* buy_listnode(LTDateType x)
{listnode*newnode=(listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("buy_listnode");//错误提示exit(-1);//节点都没创建出来,直接退出程序}newnode->data = x;//将新节点的数据初始化成我们需要的newnode->next = NULL;//不清楚插入的方式,先初始化成空newnode->prev = NULL;
}
listnode* init_listnode(listnode* phead)
{phead = buy_listnode(-1);	//-1是随便给的,初始化哨兵节点中的数据为-1,代表着没意义的数据phead->next = phead;//初始化哨兵节点,自己指向自己phead->prev = phead;return phead;
}
void print_list(listnode* phead)
{assert(phead);//哨兵节点地址不可能为空listnode* head = phead->next;//哨兵位节点不存储有效数据,因此phead->next才是头节点printf("head<=>");//纯属美观while (head != phead)//当head和phead相等意味着已经遍历完一遍链表{printf("%d<=>", head->data);head = head->next;}printf("\n");
}
void list_pushback(listnode*phead,LTDateType x)
//尾插一个新节点,此节点存储x
{listnode* newnode = buy_listnode(x);//创建一个我们需要的新节点listnode* tail = phead->prev;//先找尾,尾很显然是哨兵位节点的上一个节点tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
void list_pushfront(listnode* phead, LTDateType x)
{listnode* head = phead->next;//找到头节点listnode* newnode = buy_listnode(x);//创建新节点head->prev = newnode;newnode->next = head;phead->next = newnode;newnode->prev = phead;
}
void list_popfront(listnode*phead)
{assert(phead);if (phead->next == phead){printf("链表为空,操作失败\n");//为空就别删了return;}listnode* head = phead->next;//找到头节点phead->next = head->next;head->next->prev = phead;free(head);//链接完成,彻底删除
}
void list_popback(listnode*phead)
{assert(phead);if (phead->next == phead){printf("链表为空,操作失败\n");//为空就别删了return;}listnode* tail = phead->prev;//找到尾节点phead->prev = tail->prev;tail->prev->next = phead;free(tail);
}
listnode* list_find(listnode* phead, LTDateType x)
//哨兵节点和目标
{assert(phead);listnode* head = phead->next;//找到头节点while (head!=phead)//相等意味着已经遍历完了{if (head->data == x)//找到目标,直接返回{return head;}head = head->next;}return NULL;//遍历完还找不到,返回空指针
}
void list_insert(listnode*pos,LTDateType x)
//目标位置,和在其前面插入数据为x的节点
{if (pos == NULL)//传空意味着没找到目标{printf("目标不存在,操作失败\n");return;}listnode*newnode=buy_listnode(x);//创建新节点newnode->next = pos;newnode->prev= pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}
void list_erase(listnode* pos)
{assert(pos && pos->next != pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);
}
void destory_list(listnode* phead)
{listnode* tail = phead->prev;while (tail != phead){listnode* tmp = tail;//存储尾tail = tail->prev;//从后往前遍历free(tmp);//不需要管什么链接了,直接摧毁就行}free(phead);//单独摧毁
}

3.测试文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
void test1()
{listnode* plist=NULL;plist=init_listnode(plist);list_pushback(plist,1);list_pushback(plist,2);list_pushback(plist,3);list_pushback(plist,4);print_list(plist);
}
void test2()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);print_list(plist);list_pushfront(plist, 10086);print_list(plist);
}
void test3()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);print_list(plist);list_pushfront(plist, 10086);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);list_popfront(plist);print_list(plist);
}
void test4()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 10086);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);list_popback(plist);print_list(plist);
}
void test5()
{listnode* plist = NULL;plist = init_listnode(plist);list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 10086);print_list(plist);listnode*pos=list_find(plist,2);list_insert(pos, 888);//在2之前插入888print_list(plist);list_insert(plist->next, 666);//在头节点前插入666,与头插效用一致//可以在头插中复用这个函数print_list(plist);list_insert(plist, 520);//在哨兵节点前插入520,与尾插效用一致//可以在尾插中复用这个函数print_list(plist);
}
void test6()
{listnode* plist = NULL;plist = init_listnode(plist);//list_erase(plist->prev);//尾删,测试报错list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 3);list_pushfront(plist, 4);list_pushfront(plist, 5);print_list(plist);listnode* pos = list_find(plist, 2);list_erase(pos);//把2删除print_list(plist);list_erase(plist->next);//头删print_list(plist);list_erase(plist->prev);//尾删print_list(plist);
}
void test7()
{listnode* plist = NULL;plist = init_listnode(plist);//list_erase(plist->prev);//尾删,测试报错list_pushfront(plist, 1);list_pushfront(plist, 2);list_pushfront(plist, 3);list_pushfront(plist, 4);list_pushfront(plist, 5);destory_list(plist);
}
int main()
{test7();
}

好了,今天的分享到这里就结束了,感谢各位友友来访,祝各位友友前程似锦O(∩_∩)O

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

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

相关文章

软件测试新人入职新公司第一次分享

新公司每周都有分享会&#xff0c;本周轮到我&#xff0c;工作很多年&#xff0c;仍然处于社会主义中级阶段&#xff0c;上升高阶有待提升&#xff0c;如果想在测试的道路上继续走下去&#xff0c;还需要多多深入了解&#xff0c;多多加油将我分享的内容&#xff0c;想在这里标…

Vue实现 水果购物车业务(内包含技术要点、代码详细解释)

水果购物车业务 需求说明 渲染功能删除功能修改功能全选反选功能统计 选中的 总价 和 总数量持久化到本地 业务技术点总结&#xff1a; 渲染功能&#xff1a; v-if/v-else v-for :class删除功能&#xff1a; 点击传参 filter过滤覆盖原数组修改个数&#xff1a; 点击传参 find找…

【CSS】倾斜按钮

效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"/><meta http-equiv"X-UA-Compatible" content"IEedge"/><meta name"viewport" content"widthdevice-…

Pytest简介及jenkins集成

一、pytest介绍 pytest介绍 - unittest\nose pytest&#xff1a;基于unittest之上的单元测试框架 自动发现测试模块和测试方法 断言使用assert表达式即可 可以设置测试会话级、模块级、类级、函数级的fixtures 数据准备 清理工作 unittest&#xff1a;setUp、teardown、…

【NLP pytorch】基于BiLSTM-CRF模型医疗数据实体识别实战(项目详解)

基于BiLSTM-CRF模型医疗数据实体识别实战 1数据来源与加载1.1 数据来源1.2 数据类别名称和定义1.3 数据介绍2 模型介绍2 数据预处理2.1 数据读取2.2 数据标注2.3 数据集划分2.4 词表和标签的生成3 Dataset和DataLoader3.1 Dataset3.2 DataLoader4 BiLSTM模型定义5 CRF模型6 模型…

16. Spring Boot 统一功能处理

目录 1. 用户登录权限校验 1.1 最初用户登录验证 1.2 Spring AOP 用户统一登陆验证 1.3 Spring 拦截器 1.3.1 创建自定义拦截器 1.3.2 将自定义拦截器加入系统配置 1.4 练习&#xff1a;登录拦截器 1.5 拦截器实现原理 1.6 统一访问前缀添加 2. 统一异常处理 3. 统…

C++设计模式行为型之观察者模式

一、概述 观察者模式定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 二、示例代码 #include <list> class Subject; class Observer { public: virtual ~Observer(); virt…

心跳跟随的心形灯(STM32(HAL)+WS2812+MAX30102)

文章目录 前言介绍系统框架原项目地址本项目开发开源地址硬件PCB软件功能 详细内容硬件外壳制作WS2812级联及控制MAX30102血氧传感器0.96OLEDFreeRTOS 效果视频总结 前言 在好几年前&#xff0c;我好像就看到了焊武帝 jiripraus在纪念结婚五周年时&#xff0c;制作的一个心跳跟…

R语言中数据重塑(长宽表的转化)

学习笔记&#xff0c;仅供学习使用。 目录 1-什么是整洁的数据&#xff1f; 2-宽表变成长表 示例1&#xff1a; 示例2&#xff1a; 示例3&#xff1a; 3-长表变宽表 示例1&#xff1a; 示例2&#xff1a; 1-什么是整洁的数据&#xff1f; 按照Hadley的表述&#xf…

【redis】redis的认识和安装

目录 1.redis是什么2.Redis的特点3.安装redis4.设置远程连接4.1 开启隧道4.2 可视化客户端连接4.3 开启防火墙 5.redis常见数据类型5.1 redis的一些全局命令5.2 数据结构 6. redis的典型应用---缓存&#xff08;cache&#xff09;6.1 使用redis做缓存6.2 缓存穿透&#xff0c;缓…

华为VRP 系统基础配置

1.flash 相当于电脑的 硬盘 2.NVRAM 拿来专门存放系统配置文件 3.RAM 运行内存 4.ROM 系统引导 1.修改系统名[Huawei]sysname SWL1 2.配置登入信息 [SWL1]header shell information "Welocom to the learning" [SWL1]q <SWL1>q User interface con0 …

Excel·VBA表格横向、纵向相互转换

如图&#xff1a;对图中区域 A1:M6 横向表格&#xff0c;转换成区域 A1:C20 纵向表格&#xff0c;即 B:M 列转换成每2列一组按行写入&#xff0c;并删除空行。同理&#xff0c;反向操作就是纵向表格转换成横向表格 目录 横向转纵向实现方法1转换结果 实现方法2转换结果 纵向转横…

《吐血整理》高级系列教程-吃透Fiddler抓包教程(30)-Fiddler如何抓Android7.0以上的Https包-番外篇

1.简介 通过宏哥前边几篇文章的讲解和介绍想必大家都知道android7.0以上&#xff0c;有android的机制不在信任用户证书&#xff0c;导致https协议无法抓包。除非把证书装在系统信任的证书里&#xff0c;此时手机需要root权限。但是大家都知道root手机是非常繁琐的且不安全&…

HDFS中的sequence file

sequence file序列化文件 介绍优缺点格式未压缩格式基于record压缩格式基于block压缩格式 介绍 sequence file是hadoop提供的一种二进制文件存储格式一条数据称之为record&#xff08;记录&#xff09;&#xff0c;底层直接以<key, value>键值对形式序列化到文件中 优…

Android随笔-VPN判断

Android中判断当前网络是否为VPN /*** 判断当前网络是否为VPN* param context* return*/public static boolean hasVPN(Context context) {// 查询网络状态&#xff0c;被动监听网络状态变化ConnectivityManager cm (ConnectivityManager) context.getSystemService(Context.C…

动态规划(一)

一、背包问题 1.1 01背包问题 特点:每件物品最多只用于一次 属性包括:最大值(Max)、最小值(Min)、数量 #include<iostream> #include<algorithm>using namespace std;const int N 1010;int n,m; int v[N],w[N]; int f[N][N];int main() {cin>>n>>m;…

bash shell 中的特殊字符详解

bash shell 中的特殊字符详解 [sharp] # 井号 井号常用作注释符号 1.注释示例 # This line is a comment.2.某命令后注释&#xff0c;#号前需要添加一个空格 echo "A comment will follow." # Comment here. # ^ Note whitespace bef…

hive中时间戳与时间字符串相互转换的方法教程

时间戳是数据库常用的存放日期的形式之一&#xff0c;表示从 UTC 时间’1970-01-01 00:00:00’开始到现在的秒数&#xff0c;与常规时间格式如 ‘2018-01-01 00:00:00’可以相互转换&#xff0c;方法如下。 一、unix_timestamp 函数用法 1、unix_timestamp() 返回当前时间戳。…

Java通过实例调用getClass()方法、类名.class操作、通过运行时类获取其它信息

说明 Java Object类的getClass()函数&#xff0c;是通过对象调用的&#xff0c;是一个实例方法&#xff0c;该方法返回当前对象的运行时类。 通过类名.class可以获得和通过实例调用getClass()函数一样的信息。 获得运行时类以后&#xff0c;可以进一步获取其它信息。 代码示例…

MyCat概述

1.MyCat概述 MyCat是阿里巴巴的产品&#xff0c;他是开源的、基于Java语言编写的MySQL数据库中间件。可以像使用mysql一样来使用mycat&#xff0c;对于开发人员来说根本感觉不到mycat的存在。 MyCat下载地址&#xff1a;http://dl.mycat.org.cn/ MyCat官网&#xff1a;http:/…