带头循环双向链表详解

目录

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

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,一经查实,立即删除!

相关文章

【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、…

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. 统…

心跳跟随的心形灯(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;缓…

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>键值对形式序列化到文件中 优…

动态规划(一)

一、背包问题 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;…

MyCat概述

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

CuratorFramework接口的作用和使用

CuratorFramework接口是Apache Curator库中的核心接口之一&#xff0c;用于与ZooKeeper集群进行交互。它提供了一组丰富的方法和功能&#xff0c;用于简化与ZooKeeper的交互操作&#xff0c;包括创建、删除、读取和更新节点等。 CuratorFramework接口的主要作用是封装了底层与…

【C语言进阶篇】模拟实现通讯录 (内附源码)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言一 、 通讯录的简介1.1 联系人的类型定义1.2 通讯录的定义1.3 通讯录要实现的功能 二 、 如何…

计算机网络的定义和分类

计算机网络的定义和分类 计算机网络的定义 计算机网络的精确定义并未统一计算机网络最简单的定义是&#xff1a;一些互相连接的、自治的计算机的集合 互连:指计算机之间可以通过有线或无线的方式进行数据通信自治:是指独立的计算机&#xff0c;它有自己的硬件和软件&#xff…

Python语法:... for ... in ... if ...

Python中&#xff0c;for...in...[if]...语句是一种简洁的构建List的方法&#xff0c;从for给定的List中选择出满足if条件的元素组成新的List&#xff0c;其中if是可以省略的。下面举几个简单的例子进行说明 [for in ]: ...for ....in..... 语句. 实例如下&#xff1a; (1) …

PHP实现首字母头像

<?php $name"哈哈"; $logoletter_avatar($name);echo <img src".$logo." style" border-radius: 50%;">;function letter_avatar($text) {$total unpack(L, hash(adler32, $text, true))[1];$hue $total % 360;list($r, $g, $b) hs…

【go-zero】docker镜像直接部署API与RPC服务 如何实现注册发现?docker network 实现 go-zero 注册发现

一、场景&问题 使用docker直接部署go-zero微服务会发现API无法找到RPC服务 1、API无法发现RPC服务 用docker直接部署 我们会发现API无法注册发现RPC服务 原因是我们缺少了docker的network网桥 2、系统内查看 RPC服务运行正常API服务启动,通过docker logs 查看日志还是未…

Linux学习之正则表达式元字符和grep命令

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64。 正则表达式是一种搜索字符串的模式&#xff0c;通俗点理解&#xff0c;也就是普通字符和元字符共同组成的字符集合匹…

模板方法模式——定义算法的框架

1、简介 1.1、概述 模板方法模式是结构最简单的行为型设计模式&#xff0c;在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式&#xff0c;可以将一些复杂流程的实现步骤封装在一系列基本方法中。在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法…

vscode插件不能搜索安装

1 现象 vscode搜索自己的插件&#xff0c;报错&#xff1a; Error while fetching extensions. HXR failed2 原因 之前用vscode开发golang语言&#xff0c;设置了proxy代理&#xff0c;所以导致错误&#xff0c;删除即可 重启vscode 3 结果