网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
一、链表实现前的准备
💜1.1结构图:
~带头双向循环链表我们不要被他的结构图所吓到,只要我么深入理解他的原理,你会发现它的代码实现起来比单链表还要简单。
💜1.2初步的理解:
1.2.1 对于带头双向循环链表,要知道它的结构是比较复杂的,但是他的代码实现是比较简单的,所以,对于代码实现前,我想先讲述一下带头双向循环链表:对于带头双向循环链表我们先一步步理解,👉带头的意思就是在插入之前我们先初始化一个结构体,他并不是用来存储数据,而是保证链表有一个头节点,就可以减少讨论(当传入指针为空时,当传入指针不为空时),👉双向是指我们的结构体中存量结构体指针,一个是指向下一个,另一个是指向他的前一个,这样就保证在寻找的时候找不到前一个的位置。 👉循环是指在尾节点我们并不让他指向空指针,而是让他指向头节点,而头节点的前一个也不让他指向空指针,而是让他指向尾节点,这样就会达成一个循环的作用,他的好处就是在于我们们寻找尾节点的时候就不用遍历了,而是通过找头节点的前一个就能找到尾节点,从而达到时间复杂度为O(1)来完成尾插和尾删!
二、带头双向链表功能实现前的准备
🤎 2.1链表实现所需要的头文件:
#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
💖 这里是害怕有的小伙伴有的头文件并不知道^ _ ^ 。
🤎2.2链表实现的初始化:
LTNode* ListInit() //返回结构体指针
{
LTNode*guard=(LTNode*)malloc(sizeof(LTNode)); //这里就像定义一个哨兵,也就是头节点,就不用再担心头节点是不是NULL了
if(guard==NULL) //确保能够正常扩容
{
perror("ListNodeInit");
}
guard->next=guard; //头节点的下一个先指向自己
guard->prev=guard; //头节点的上一个也先指向自己
return guard;
}
🤎2.2链表实现的打印:
void ListNodePrint(LTNode* phead)
{
assert(phead); //保证传入的不是空指针
LTNode* cur =phead->next; //定一个结构体指针进行遍历打印
printf("guard<=>"); //这里是为了打印效果好看一点
while(cur!=phead)
{
printf("%d<=>",cur->data);
cur=cur->next;
}
printf("\n");
}
🤎2.3定义一个节点为了实现插入:
LTNode* BuyListNode(LTDataType x)
{
LTNode* newnode=(LTNode*)malloc(sizeof(ListNode)); //节点申请空间
if(newnode==NULL)
{
perror("BuyListNode");
}
newnode->data=x; //节点所带的值
newnode->prev=NULL;
newnode->next=NULL;
return newnode;
}
🤎2.4判断节点是否为空节点:
bool ListEmpty(LTNode*phead)
{
assert(phead);
return phead->next==phead; //如果头和头的下一个指向同一个则返回真,证明只有一个哨兵
}
🤎2.5链表实现后的销毁:
void ListDestroy(LTNode*phead)
{
LTNode*cur=phead->next; //定义一个结构体指针进行遍历销毁
while(cur!=phead)
{
LTNode*next=cur->next;
free(cur);
cur=next;
}
free(phead); //最后释放头节点
}
3、链表功能的实现
❤️3.1链表的头插:
void ListPushFront(LTNode*phead,LTDataType x)
{
assert(phead);
LTNode* newnode=BuyListNode(x); //先定义一个节点进行头插
newnode->next=phead->next; //这里要考虑顺序问题,不然很容易出错,先让定义的next指向哨兵的下一个,然后哨兵下一个的prev指向我们新的节点
//然后让我们我们哨兵的next指向新节点,新节点的prev指向哨兵, 这里如果不理解的可以画一下图
//或者我么先定义一个节点把哨兵的下一个进行保存这里就不用考虑先后顺序了,大家可以试一试
phead->next->prev=newnode;
phead->next=newnode;
newnode->prev=phead;
}
❤️3.2链表的尾插:
void ListPushBack(LTNode*phead,LTDataType x)
{
assert(phead);
LTNode* newnode=BuyListNode(x); //定义一个新节点
LTNode* tail=phead->prev; //找到尾节点
tail->next=newnode; //然后就是交换结构体里面的next prev指向的位置来实现插入。头插我已经讲述过程了,主要是大家尝试一下画画图
newnode->prev=tail;
newnode->next=phead;
phead->prev=newnode;
}
❤️3.3链表的头删:
void ListPopFront(LTNode*phead)
{
assert(phead);
assert(!ListEmpty(phead)); //这里防止只剩下一个哨兵,如果只剩下一个哨兵还进行删除就强制类型报错
LTNode*first=phead->next; //定义一个first指向哨兵的next
LTNode* second=first->next; //在定义一个second指向first的next
phead->next=second;
second->prev=phead;
free(first); //把first删除后进行free
first=NULL; //first指针指向空,防止出现野指针
}
❤️3.4链表的尾删:
void ListPopBack(LTNode*phead)
{
assert(phead);
assert(!ListEmpty(phead));//这里防止只剩下一个哨兵,如果只剩下一个哨兵还进行删除就强制类型报错
LTNode*tail=phead->prev;
tail->prev->next=phead;
phead->prev=tail->prev;
free(tail);
tail=NULL;
}
❤️3.5链表的pos位插入:
void ListInsert(LTNode*pos,LTDataType x)
{
assert(pos); //保证传入的pos位不是空指针
LTNode*prev=pos->prev; //找到pos位的前一位
LTNode*newnode=BuyListNode(x); //定义一个新节点进行插入
pos->prev=newnode;
newnode->next=pos;
prev->next=newnode;
newnode->prev=prev;
}
❤️3.6链表的pos位删除:
void ListErase(LTNode*pos)
{
LTNode*prev=pos->prev;//找到pos位的前一个
LTNode*next=pos->next; //找到pos的后一个
prev->next=next;
next->prev=prev;
free(pos); //释放pos
}
❤️3.7链表的查找:
LTNode* ListNodeFind(LTNode*phead,LTDataType x) //查找后返回指针,如果找到返回找的到的结构体指针,没有找到返回空指针
{
assert(phead);
LTNode* cur=phead->next; //定义一个结构体指针进行遍历寻找
while(cur!=phead)
{
if(cur->data==x)
{
return cur; //找到后返回的指针
}
cur=cur->next;
}
return NULL;
}
4、带头双向循环链表的源码
💚4.1 List.h
#ifndef List_hpp #define List_hpp #include <stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int LTDataType; typedef struct ListNode { struct ListNode*next; struct ListNode*prev; LTDataType data; }LTNode; LTNode* ListInit(); LTNode* BuyListNode(LTDataType x); void ListPushBack(LTNode*phead,LTDataType x); void ListNodePrint(LTNode* phead); void ListPushFront(LTNode*phead,LTDataType x); void ListPopBack(LTNode*phead); bool ListEmpty(LTNode*phead); void ListPopFront(LTNode*phead); size_t ListSize(LTNode*phead); LTNode* ListNodeFind(LTNode*phead,LTDataType x); void ListInsert(LTNode*pos,LTDataType x); void ListErase(LTNode*pos); void ListDestroy(LTNode*phead); #endif /* List_hpp */
💚4.2List.c
#include "List.hpp" LTNode* ListInit() { LTNode*guard=(LTNode*)malloc(sizeof(LTNode)); //这里就像定义一个哨兵,也就是头节点,就不用再担心头节点是不是NULL了 if(guard==NULL) //确保能够正常扩容 { perror("ListNodeInit"); } guard->next=guard; //头节点的下一个先指向自己 guard->prev=guard; //头节点的上一个也先指向自己 return guard; } LTNode* BuyListNode(LTDataType x) { LTNode* newnode=(LTNode*)malloc(sizeof(ListNode)); //节点申请空间 if(newnode==NULL) { perror("BuyListNode"); } newnode->data=x; //节点所带的值
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
newnode->data=x; //节点所带的值
[外链图片转存中…(img-ju3T0Ygv-1715464673705)]
[外链图片转存中…(img-baa4S9pZ-1715464673705)]
[外链图片转存中…(img-pdG90xcq-1715464673706)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新