数据结构体进阶链表【带头双向循环链表,单向链表的优化,从根部解决了顺序表的缺点】一文带你深入理解链表_求单向链表长度的算法怎么优化(3)

img
img
img

既有适合小白学习的零基础资料,也有适合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就行啦,这里我给大家讲一个思路,对于他的实现,大家下去可以尝试一下

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

使pos指向phead就行啦,这里我给大家讲一个思路,对于他的实现,大家下去可以尝试一下

[外链图片转存中…(img-DtB1VtZn-1715464754917)]
[外链图片转存中…(img-Gx1hXmPv-1715464754918)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
链表是一种数据结构,它由节点组成,每个节点包含两部分:数据和指向下一个节点的指针。链表中的节点按照一定的顺序排列,并通过指针相互连接起来,形成一个链式结构。链表的一个重要特点是不需要连续的内存空间,因此可以动态分配内存,这使得链表非常适合处理插入和删除节点这样的操作。 与链表相比,二叉树是一种更复杂的数据结构,它由节点和指向子节点的指针组成。与链表不同的是,在二叉树中每个节点最多有两个子节点,左子节点和右子节点。二叉树有多种不同的变种,包括二叉搜索树、AVL树、红黑树等。 在本关中,你需要建立一个带头结点的单向链表,这意味着在链表的开头添加一个特殊的头结点,它不包含任何数据,但是包含指向链表第一个实际节点的指针。头结点的作用是方便对链表的操作,例如插入、删除、遍历等。你需要熟悉链表的基本操作,例如创建、插入、删除、查找、反转等,并能够灵活运用这些操作来实现本关的任务。 在C语言中,链表通常使用结构体来表示节点,例如: ``` struct ListNode { int val; struct ListNode *next; }; ``` 其中val用来保存节点的数据,next用来指向下一个节点。通过结构体指针可以访问节点的成员变量,例如: ``` struct ListNode *node = malloc(sizeof(struct ListNode)); node->val = 1; node->next = NULL; ``` 这段代码创建了一个节点,赋值为1,next指针为NULL,表示这是链表的最后一个节点。对于带头结点的链表,可以用类似的方式定义头结点: ``` struct ListNode *head = malloc(sizeof(struct ListNode)); head->next = NULL; ``` 这段代码创建了一个头结点,next指针为NULL,表示这是一个空链表。注意,在访问链表节点时,需要先检查指针是否为NULL,以防止访问空指针导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值