链表之带头双向循环链表(C语言版)

我们之前已经介绍过链表的知识了,这里我们直接开始实现带头双向循环链表

数据结构之单链表(不带头单向非循环链表)-CSDN博客

第一步:定义结构体

//定义结构体
typedef int SLTDateType;
typedef struct Listnode
{SLTDateType date;struct Listnode* prev;struct Listnode* next;
}SL;

注意:我们这里要两个指针,一个指向链表前一个,一个指向链表下一个。

第二步:实现开辟空间函数

//开辟空间函数定义
SL* BuyListNode(SLTDateType x)
{SL* nownode = (SL*)malloc(sizeof(SL));//动态开辟一个结构体大小的空间nownode->date = x;//将开辟的空间中结构体成员date赋值为xnownode->next = NULL;//将该结构体成员尾指针置为空nownode->prev = NULL;//将该结构体成员头指针置为空return nownode;//返回该结构体地址
}

第三步:实现初始化函数

//初始化函数定义
SL* ListInit()
{//该函数是创造一个head结构体放在链表的开头,满足带头链表SL* phead = BuyListNode(0);//将该结构开辟空间,并且将成员date赋值0phead->next = phead;//将phead的尾指针指向自己phead->prev = phead;//将phead的头指针指向自己return phead;//返回该结构体地址
}

第四步:实现双向链表打印

// 双向链表打印定义
void ListPrint(SL* phead)
{assert(phead);SL* cur = phead->next;while (cur != phead){printf("%d ", cur->date);cur = cur->next;}printf("\n");
}

第五步:实现四大接口(头删 尾删  头插   尾插)

//双向链表尾插定义
void ListPushBack(SL* phead, SLTDateType x)
{/*尾插的实现可以分成四步:1.将链表的最后一个元素尾指针从指向链表第一个元素变成指向开辟的结构体2.将开辟的结构体头指针指向链表最后一个元素3.将链表的第一个元素头指针从指向链表最后一个元素变成指向开辟的结构体4.将开辟的结构体尾指针指向链表第一个元素*/assert(phead);SL* tail = phead->prev;//定义一个结构体指针指向头位置的头指针指向的位置,即为链表的最后一个元素SL* nownode = BuyListNode(x);//开辟一个结构体空间tail->next = nownode;//将tail结构体尾指针指向开辟的结构体的位置nownode->prev = tail;//将开辟的结构体的头指针指向tail的位置,即为链表的最后一个元素nownode->next = phead;//将开辟的结构体的尾指针指向链表的开头,即为pheadphead->prev = nownode;//将phead的头指针指向开辟的结构体nownode
}
// 双向链表头插定义
void ListPushFront(SL* phead, SLTDateType x)
{assert(phead);//检查assert(phead->next!=NULL);//检查SL* nownode = BuyListNode(x);SL* next = phead->next;phead->next = nownode;nownode->prev = phead;nownode->next = next;next->prev = nownode;
}
// 双向链表尾删定义
void ListPopBack(SL* phead)
{assert(phead);//检查assert(phead->next != NULL);//检查SL* first = phead->prev;SL* second =first->prev;phead->prev = second;second->next = phead;free(first);//释放firstfirst = NULL;//指针置为空
}
// 双向链表头删定义
void ListPopFront(SL* phead)
{assert(phead);SL* first = phead->next;SL* second = first->next;phead->next =second ;second->prev =phead ;free(first);first = NULL;
}

大家可以自己画图理解下,我的第一个注释写的详细

下面我们检验代码:

#include "SList.h"
void test1()
{//我们实现尾插1 2 3,再头插3 2 1,然后打印观察// 再头删去 3 2 ,尾删 3 2,然后打印观察//调用函数测试SL* plist = ListInit();//双向链表尾插调用ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);// 双向链表头插调用ListPushFront(plist,3);ListPushFront(plist,2);ListPushFront(plist,1);//双向链表打印调用ListPrint(plist);// 双向链表头删调用ListPopFront(plist);ListPopFront(plist);// 双向链表尾删调用ListPopBack(plist);ListPopBack(plist);// 双向链表打印调用ListPrint(plist);
}
int main()
{test1();return 0;
}

结果:

噢耶,对了,现在我们可以继续下一步了!

第六步:实现特殊接口

// 双向链表查找定义
SL* ListFind(SL* phead, SLTDateType x)
{assert(phead);SL* cur = phead->next;while (cur != phead){if (cur->date ==x ){return cur;}cur = cur->next;}return NULL;
}
// 双向链表在pos的前面进行插入定义
void ListInsert(SL* pos, SLTDateType x)
{assert(pos);SL* first = pos->prev;SL* newnode = BuyListNode(x);first->next= newnode;newnode->prev=first;newnode->next=pos;pos->prev=newnode;
}
// 双向链表删除pos位置的结点定义
void ListErase(SL* pos)
{assert(pos);SL* first = pos->prev;SL* second = pos->next;first->next = second;second->prev = first;
}
// 双向链表销毁定义
void ListDestory(SL* phead)
{assert(phead);SL* cur = phead->next;while (cur != phead){SL* cur2 = cur->next;free(cur);cur = cur2;}free(phead);phead = NULL;
}

再次进行检查:

void test2()
{SL* plist = ListInit();//双向链表尾插调用ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);// 双向链表打印调用ListPrint(plist);SL* pos = ListFind(plist, 2);if(pos != NULL){// 双向链表在pos的前面进行插入ListInsert(pos, 30);// 双向链表打印调用ListPrint(plist);// 双向链表删除pos位置的结点ListErase(pos);// 双向链表打印调用ListPrint(plist);}free(pos);pos = NULL;// 双向链表销毁ListDestory(plist);
}
int main()
{//test1();test2();return 0;
}

结果:

综上:我们成功实现了带头双向循环链表!

全部代码如下:

这个是SList.c文件

#include "SList.h"//开辟空间函数定义
SL* BuyListNode(SLTDateType x)
{SL* nownode = (SL*)malloc(sizeof(SL));//动态开辟一个结构体大小的空间if (nownode == NULL){perror(nownode);exit(-1);}nownode->date = x;//将开辟的空间中结构体成员date赋值为xnownode->next = NULL;//将该结构体成员尾指针置为空nownode->prev = NULL;//将该结构体成员头指针置为空return nownode;//返回该结构体地址
}
//初始化函数定义
SL* ListInit()
{//该函数是创造一个head结构体放在链表的开头,满足带头链表SL* phead = BuyListNode(0);//将该结构开辟空间,并且将成员date赋值0phead->next = phead;//将phead的尾指针指向自己phead->prev = phead;//将phead的头指针指向自己return phead;//返回该结构体地址
}
// 双向链表打印定义
void ListPrint(SL* phead)
{assert(phead);SL* cur = phead->next;	while (cur != phead){printf("%d ", cur->date);cur = cur->next;}printf("\n");
}
//双向链表尾插定义
void ListPushBack(SL* phead, SLTDateType x)
{/*尾插的实现可以分成四步:1.将链表的最后一个元素尾指针从指向链表第一个元素变成指向开辟的结构体2.将开辟的结构体头指针指向链表最后一个元素3.将链表的第一个元素头指针从指向链表最后一个元素变成指向开辟的结构体4.将开辟的结构体尾指针指向链表第一个元素*/assert(phead);SL* tail = phead->prev;//定义一个结构体指针指向头位置的头指针指向的位置,即为链表的最后一个元素SL* nownode = BuyListNode(x);//开辟一个结构体空间tail->next = nownode;//将tail结构体尾指针指向开辟的结构体的位置nownode->prev = tail;//将开辟的结构体的头指针指向tail的位置,即为链表的最后一个元素nownode->next = phead;//将开辟的结构体的尾指针指向链表的开头,即为pheadphead->prev = nownode;//将phead的头指针指向开辟的结构体nownode
}
// 双向链表头插定义
void ListPushFront(SL* phead, SLTDateType x)
{assert(phead);//检查assert(phead->next!=NULL);//检查SL* nownode = BuyListNode(x);SL* next = phead->next;phead->next = nownode;nownode->prev = phead;nownode->next = next;next->prev = nownode;
}
// 双向链表尾删定义
void ListPopBack(SL* phead)
{assert(phead);//检查assert(phead->next != NULL);//检查SL* first = phead->prev;SL* second =first->prev;phead->prev = second;second->next = phead;free(first);//释放firstfirst = NULL;//指针置为空
}
// 双向链表头删定义
void ListPopFront(SL* phead)
{assert(phead);SL* first = phead->next;SL* second = first->next;phead->next =second ;second->prev =phead ;free(first);first = NULL;
}
// 双向链表查找定义
SL* ListFind(SL* phead, SLTDateType x)
{assert(phead);SL* cur = phead->next;while (cur != phead){if (cur->date ==x ){return cur;}cur = cur->next;}return NULL;
}
// 双向链表在pos的前面进行插入定义
void ListInsert(SL* pos, SLTDateType x)
{assert(pos);SL* first = pos->prev;SL* newnode = BuyListNode(x);first->next= newnode;newnode->prev=first;newnode->next=pos;pos->prev=newnode;
}
// 双向链表删除pos位置的结点定义
void ListErase(SL* pos)
{assert(pos);SL* first = pos->prev;SL* second = pos->next;first->next = second;second->prev = first;
}
// 双向链表销毁定义
void ListDestory(SL* phead)
{assert(phead);SL* cur = phead->next;while (cur != phead){SL* cur2 = cur->next;free(cur);cur = cur2;}free(phead);phead = NULL;
}

头文件:SList.h文件

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//定义结构体
typedef int SLTDateType;
typedef struct Listnode
{SLTDateType date;struct Listnode* prev;struct Listnode* next;
}SL;
//双向链开辟空间函数声明
SL* BuyListNode(SLTDateType x);
//双向链初始化函数声明
SL* ListInit();
// 双向链表打印声明
void ListPrint(SL* phead);
//双向链表尾插声明
void ListPushBack(SL* phead, SLTDateType x);
// 双向链表头插声明
void ListPushFront(SL* plist, SLTDateType x);
// 双向链表尾删声明
void ListPopBack(SL* phead);
// 双向链表头删声明
void ListPopFront(SL* phead);
// 双向链表查找声明
SL* ListFind(SL* phead, SLTDateType x);
// 双向链表在pos的前面进行插入声明
void ListInsert(SL* pos, SLTDateType x);
// 双向链表删除pos位置的结点声明
void ListErase(SL* pos);
// 双向链表销毁声明
void ListDestory(SL* phead);

下面是我们的调试文件:test.c

#include "SList.h"
void test1()
{//我们实现尾插1 2 3,再头插3 2 1,然后打印观察// 再头删去 3 2 ,尾删 3 2,然后打印观察//调用函数测试SL* plist = ListInit();//双向链表尾插调用ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);// 双向链表头插调用ListPushFront(plist,3);ListPushFront(plist,2);ListPushFront(plist,1);//双向链表打印调用ListPrint(plist);// 双向链表头删调用ListPopFront(plist);ListPopFront(plist);// 双向链表尾删调用ListPopBack(plist);ListPopBack(plist);// 双向链表打印调用ListPrint(plist);
}
void test2()
{SL* plist = ListInit();//双向链表尾插调用ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);// 双向链表打印调用ListPrint(plist);SL* pos = ListFind(plist, 2);if(pos != NULL){// 双向链表在pos的前面进行插入ListInsert(pos, 30);// 双向链表打印调用ListPrint(plist);// 双向链表删除pos位置的结点ListErase(pos);// 双向链表打印调用ListPrint(plist);}free(pos);pos = NULL;// 双向链表销毁ListDestory(plist);
}
int main()
{//test1();test2();return 0;
}

希望大家坚持学下去,在链表这里你可以发现无穷的乐趣,当然建议大家链表这里还是多刷题,这样才能帮助我们更好理解它。

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

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

相关文章

字符设备驱动框架的编写

一. 简介 我们在学习裸机或者 STM32 的时候关于驱动的开发就是初始化相应的外设寄存器&#xff0c;在 Linux 驱动开发中&#xff0c;肯定也是要初始化相应的外设寄存器。 只是在 Linux 驱动开发中&#xff0c; 我们需要按照其规定的框架来编写驱动&#xff0c;所以说学 …

【HTML5、CSS3】新增特性总结!

文章目录 23 HTML5 新增特性23.1 语义化标签23.2 多媒体标签23.2.1 视频<video>标签23.2.2 音频<audio>标签 23.3 input属性值23.4 表单属性 24 CSS3 新增特性24.1 属性选择器24.2 结构伪类选择器24.2.1 选择第n个元素24.2.2 常用的6个结构伪类选择器 24.3 伪元素选…

如何用Python向图像中加入噪声

我们在做机器视觉项目的过程中&#xff0c;有的时候需要向图像中加入噪声。Pytorch本身不支持类似的功能&#xff0c;如果自己写的话&#xff0c;不但麻烦&#xff0c;而且容易出错。好在skimage支持这个功能。代码如下&#xff1a; import skimage import matplotlib.pyplot …

抚琴成一快-布鲁斯

布鲁斯 0.理论1.音阶1.大调布鲁斯音阶2.小调布鲁斯音阶 1.基础1.shuffle节奏制音2.十二小节3.和弦4.小调五声音阶 2.演奏手法1.Lamp and Lamp1.基础和声进行2.进阶和声进行1.quick change2. call and respond:3.回旋句 2.Box1.基础和声进行2.进阶和声进行 3.Boogie1.基础节奏2.…

【教学类-06-19】20231217 (按“列”正序题)X-Y之间“加法题+题”(1页最多0-13。填满115空格)

作品展示&#xff1a;按列排序&#xff0c;从小到大正序&#xff08;没有大量空格&#xff09; 1.会有空格做分割线&#xff0c;上面部分是所有的小到大正序加法&#xff0c;下面的部分就是正序题目的不重复随机抽取题目&#xff08;乱序题&#xff09; 2、包含分割空格&…

实验记录:深度学习模型收敛速度慢有哪些原因

深度学习模型收敛速度慢有哪些原因&#xff1f; 学习率设置不当&#xff1a; 学习率是算法中一个重要的超参数&#xff0c;它控制模型参数在每次迭代中的更新幅度。如果学习率过大&#xff0c;可能会导致模型在训练过程中的振荡&#xff0c;进而影响到收敛速度&#xff1b;如果…

【Windows】windows11右键默认显示更多选项的办法

Windows11系统的右键菜单显示&#xff0c;需要多点一次“显示更多选项”才能看到所有菜单内容&#xff0c;按下面步骤简单设置一下就能恢复成Windows经典的右键菜单显示。 1. 2.输入命令【reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a…

Redis - RDB与AOF持久化技术

Redis 持久化技术 RDB 是默认持久化方式&#xff0c;但 Redis 允许 RDB 与 AOF 两种持久化技术同时 开启&#xff0c;此时系统会使用 AOF 方式做持久化&#xff0c;即 AOF 持久化技术的优先级要更高。同样的道 理&#xff0c;两种技术同时开启状态下&#xff0c;系…

css的元素显示模式(有单行文字垂直居中,侧边栏等案例)

目录 1. 什么是元素的显示模式 2. 元素显示模式的类型 块元素 行内元素 行内块元素 3. 元素显示模式的转换 4.文字垂直居中 5.具体实现案例 1. 什么是元素的显示模式 定义&#xff1a;元素显示模式就是元素&#xff08;标签&#xff09;以什么方式进行显示&#xff0c;…

HarmonyOS鸿蒙应用开发——数据持久化Preferences封装

文章目录 数据持久化简述基本使用与封装测试用例参考 数据持久化简述 数据持久化就是将内存数据通过文件或者数据库的方式保存到设备中。HarmonyOS提供两两种持久化方案&#xff1a; Preferences&#xff1a;主要用于保存一些配置信息&#xff0c;是通过文本的形式存储的&…

TCP/IP详解——FTP 协议,Telnet协议

文章目录 1. FTP 协议1.1 FTP的应用1.2 FTP传输文件的过程1.3 FTP传输模式1.4 主动模式&#xff08;Active Mode&#xff09;1.5 Active Mode 抓包分析1.6 被动模式&#xff08;Passive Mode&#xff09;1.7 Passive Mode 抓包分析 2. Telnet 协议2.1 Telnet 概念2.2 Telnet 协…

【网络安全】网络防护之旅 - Java安全机制探秘与数字证书引爆网络防线

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《网络安全之道 | 数字征程》⏰墨香寄清辞&#xff1a;千里传信如电光&#xff0c;密码奥妙似仙方。 挑战黑暗剑拔弩张&#xff0c;网络战场誓守长。 目录 &#x1f608;1. 初识网络安…

Web前端-CSS(文本样式)

文章目录 1.font字体1.1 font-size:大小1.2 font-family:字体1.3 font-weight:字体粗细1.4 font-style:字体风格1.5 font总结 2. css外观属性2.1 color:文本颜色2.2 text-align:文本水平对齐方式2.3 line-height:行间距2.4 text-indent:首行缩进2.5 text-decoration 文本的装饰…

挑战52天学小猪佩奇笔记--day25

52天学完小猪佩奇--day25 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day25 的视频内容总结&#xff0c;方便复习。强烈建议大家去关注一波UP&#xff0c;配合UP视频学习。 注&#xff1a;这集开始变成一段一段的猜台词&#xff0c;加…

代码随想Day39 | 62.不同路径、63. 不同路径 II

62.不同路径 每次向右或者向下走两个选择&#xff0c;定义dp数组dp[i][j] 为到达索引ij的路径和&#xff0c;状态转移公式为 dp[i][j]dp[i-1][j]dp[i][j-1]&#xff0c;初始状态的第一行和第一列为1&#xff0c;从左上到右下开始遍历即可。详细代码如下&#xff1a; class Sol…

Java并发(十九)----Monitor原理及Synchronized原理

1、Java 对象头 以 32 位虚拟机为例 普通对象 |--------------------------------------------------------------| | Object Header (64 bits) | |------------------------------------|-------------------------| | Mark W…

MySQL 报错 You can‘t specify target table for update in FROM clause解决办法

You can’t specify target table for update in FROM clause 其含义是&#xff1a;不能在同一表中查询的数据作为同一表的更新数 单独执行复合查询是正常的&#xff0c;如下&#xff1a; 但是当执行子查询删除命令时&#xff0c;报如下错误 DELETE FROM abpusers WHERE Id I…

简单介绍十款可以免费使用的API测试工具

API开发应该是后端开发最常见的工作&#xff0c;而调试和测试API是非常关键的&#xff0c;这篇文章简单介绍几款常用的工具以供大家参考。 SoapUI SoapUI是很老牌的工具的&#xff0c;在之前Webservice盛行的时候经常会用到。 现在官方推出了Pro版本的ReadyAPI&#xff0c;但要…

Python glob

参考文章&#xff1a; Python 中glob.glob()、glob.iglob&#xff08;&#xff09;的使用-CSDN博客 Python 中glob.glob()的使用 glob.glob(path)的功能&#xff1a; 返回符合path格式的所有文件的路径&#xff0c;以list存储返回。 path的表示方法&#xff1a; 利用匹配符…

数据科学知识库

​ 我的博客是一个技术分享平台&#xff0c;涵盖了机器学习、数据可视化、大数据分析、数学统计学、推荐算法、Linux命令及环境搭建&#xff0c;以及Kafka、Flask、FastAPI、Docker等组件的使用教程。 在这个信息时代&#xff0c;数据已经成为了一种新的资源&#xff0c;而机…