链表之带头双向循环链表(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.hqwc.cn/news/283408.html

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

相关文章

【消息中间件】Rabbitmq的基本要素、生产和消费、发布和订阅

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、消息队列的基本要素1.队列:queue2.交换机:exchange3.事件:routing_key4.任务:task 二、生产消费模式1.安装pika2.模拟生产者进程3.模…

Web前端-HTML(常用标签)

文章目录 1. HTML常用标签1.1 排版标签1&#xff09;标题标签h (熟记)2&#xff09;段落标签p ( 熟记)3&#xff09;水平线标签hr(认识)4&#xff09;换行标签br (熟记)5&#xff09;div 和 span标签(重点)6&#xff09;排版标签总结 1.2 标签属性1.3 图像标签img (重点)1.4 链…

shell子进程管理

简介 在我们平时写代码过程中&#xff0c;可能经常会遇到串行执行速度慢 &#xff0c;串行无法执行多个任务&#xff0c;这时便需要使用子进程同时执行。使用父进程创建子进程时&#xff0c;子进程会复制父进程的内存、文件描述符和其他相关信息。当然&#xff0c;子进程可以独…

Web前端-JavaScript(js表达式)

文章目录 JavaScript基础第01天1.编程语言概述1.1 编程1.2 计算机语言1.2.1 机器语言1.2.2 汇编语言1.2.3 高级语言 1.4 翻译器 2.计算机基础2.1 计算机组成2.2 数据存储2.3 数据存储单位2.4 程序运行 3.初始JavaScript3.1 JavaScript 是什么3.2 JavaScript的作用3.3 HTML/CSS/…

医疗智能化革命:AI技术引领医疗领域的创新进程

一、“AI”医疗的崛起 随着人工智能&#xff08;AI&#xff09;技术的崛起&#xff0c;"AI"医疗正在以惊人的速度改变着医疗行业的面貌。AI作为一种强大的工具&#xff0c;正在为医疗领域带来前所未有的创新和突破。它不仅在医学影像诊断、病理学分析和基因组学研究等…

tomcat错误

Error running Tomcat8: Address localhost:1099 is already in use window环境&#xff0c;打开cmd netstat -ano | findstr :1099发现对应PID为24732 结束PID taskkill /PID 24732 /F

R语言【rgbif】——occ_search对待字符长度大于1500的WKT的特殊处理真的有必要吗?

一句话结论&#xff1a;只要有网有流量&#xff0c;直接用长WKT传递给参数【geometry】、参数【limit】配合参数【start】获取所有记录。 当我在阅读 【rgbif】 给出的用户手册时&#xff0c;注意到 【occ_search】 强调了 参数 【geometry】使用的wkt格式字符串长度。 文中如…

配置Nginx解决跨域问题

Nginx 中将前端请求中的所有以 “/apiUrl” 开头的路径代理到 http://192.12.200.101:9813 例如&#xff1a; /apiUrl/login > http://192.12.200.101:9813/login 配置nginx环境 进入Nginx 的配置文件编辑界面: sudo nano /etc/nginx/conf.d/default.conf开始编辑 defaul…

计算机网络(1):开始

计算机网络&#xff08;1&#xff09;&#xff1a;开始 计算机网络在信息时代中的作用 21世纪的一些重要特征就是数字化、网络化和信息化&#xff0c;是一个以网络为核心的信息时代。要实现信息化就必须依靠完善的网络&#xff0c;因为网络可以非常迅速地传递信息。因此网络现…

STM32/STM8资源节约主义设计方式

STM32/STM8资源节约主义设计方式 在小资源芯片进行代码设计时&#xff0c;如STM32C0系列&#xff0c;STM8系列&#xff0c;因为官方库本身要包含各种场景应用特征的支持&#xff0c;所以会有一些冗余的代码占用更多FLASH空间。当需要实现资源占用最简化设计方式时&#xff0c;…

AAAI中稿心得

很幸运我们的一篇工作中稿了AAAI2024&#xff0c;题目是 Self-Prompt Mechanism for Few-Shot Image Recognition. 很高兴能在研二的上学期中稿一篇a会保底&#xff0c;也是我中稿的第一篇工作&#xff0c;成为我申请博士的资本。最重要的是&#xff0c;让枯燥无味的科研&#…

3.3【窗口】窗口的几何形状(二,窗口属性)

写在前面 应用程序使用窗口来显示内容。一些属性决定了窗口及其内容的大小和位置。其他属性决定了窗口内容的外观和解释。 了解窗口属性引用的两个坐标系非常重要。如果你知道你正在使用的坐标系,那么为你的窗口属性选择设置值会容易得多,并且会更有意义。 一,显示相关属…