既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
❤️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; //节点所带的值 newnode->prev=NULL; newnode->next=NULL; return newnode; } void ListNodePrint(LTNode* phead) { assert(phead); //保证传入的不是空指针 LTNode* cur =phead->next; //定一个结构体指针进行遍历打印 printf("guard<=>"); //这里是为了打印效果好看一点 while(cur!=phead) { printf("%d<=>",cur->data); cur=cur->next; } printf("\n"); } 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; } 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; } bool ListEmpty(LTNode*phead) { assert(phead); return phead->next==phead; //如果头和头的下一个指向同一个则返回真,证明只有一个哨兵 } 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; } 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指针指向空,防止出现野指针 } size_t ListSize(LTNode*phead) { assert(phead); LTNode*cur=phead->next; size_t n=0; while(cur!=phead) { ++n; cur=cur->next; } return n; } 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; } 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; } void ListErase(LTNode*pos) { LTNode*prev=pos->prev;//找到pos位的前一个 LTNode*next=pos->next; //找到pos的后一个 prev->next=next; next->prev=prev; free(pos); //释放pos } void ListDestroy(LTNode*phead) { LTNode*cur=phead->next; //定义一个结构体指针进行遍历销毁 while(cur!=phead) { LTNode*next=cur->next; free(cur); cur=next; } free(phead); //最后释放头节点 }
💚4.3Test.c
#include "List.hpp" void test1() { LTNode* plist=ListInit(); ListPushBack(plist,1); ListPushBack(plist,2); ListPushBack(plist,3); ListPushBack(plist,4); ListPushBack(plist,5); ListPopBack(plist); ListPopBack(plist); ListNodePrint(plist); ListDestroy(plist); plist=NULL; } void test2() { LTNode* plist=ListInit(); ListPushFront(plist,1); ListPushFront(plist,2); ListPushFront(plist,3); ListPushFront(plist,4); ListPushFront(plist,5); ListPopFront(plist); ListPopFront(plist); size_t ret=ListSize(plist); printf("%zu\n",ret); ListNodePrint(plist); } int main() { test1(); test2(); return 0; }
总结:
对于带头双向循环链表的实现我还是那句话,不要害怕!!!他只是结构体比较复杂,但是代码实现它确比较容易的,它不像顺序表那样要考虑是否要扩容,也不需要像单链表那样要考虑传入是否为空指针,所以他的实现还是比较容易的,而本文对于他的所有实现,我想小马应该写的很详细啦,如果有不懂的可以私信问我或者直接在评论区中问我啦!!
再者,其实思考一下,双向循环链表pos插入删除时,当我们pos等于phead时,你会发现他就像尾插和尾删,发现就不需要我们写的那么复杂啦,只需要调用一下pos函数,使pos指向phead就行啦,这里我给大家讲一个思路,对于他的实现,大家下去可以尝试一下
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
使pos指向phead就行啦,这里我给大家讲一个思路,对于他的实现,大家下去可以尝试一下
[外链图片转存中…(img-DtB1VtZn-1715464754917)]
[外链图片转存中…(img-Gx1hXmPv-1715464754918)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!