• 数据结构-双向链表


    前言:

    在单链表那一篇博客中介绍了单链表和双向链表的优缺点,所以此篇博客直接分享怎样实现一个带头双向循环链表。

    单链表博客:

    http://t.csdnimg.cn/Kw7zLicon-default.png?t=N7T8http://t.csdnimg.cn/Kw7zL

    1.头文件中的声明:

    首先我们需要写一个结构体,双向带头链表的话需要一个前驱指针prev和一个后驱指针next,前驱指针的作用是方便找尾节点,因为头节点的prev指向的就是最后一个节点,后驱指针next的作用是方便插入和找头节点。

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. typedef int LTDataType;
    6. typedef struct Listnode
    7. {
    8. struct ListNode* prev;
    9. struct ListNode* next;
    10. LTDataType data;
    11. }LTNode;
    12. LTNode* BuyLTNode(LTDataType x);//创建节点
    13. LTNode* LTInit();//初始化
    14. void LTPrint(LTNode* phead);//打印
    15. void LTPushBack(LTNode* phead, LTDataType x);//尾插
    16. void LTPopBack(LTNode* phead);//尾删
    17. void LTPushFront(LTNode* phead, LTDataType x);//头插
    18. void LTPopFront(LTNode* phead);//头删
    19. int LTSize(LTNode* phead);//求有效数据
    20. LTNode* LTFind(LTNode* phead, LTDataType x);
    21. void LTInsert(LTNode* pos ,LTDataType x);//在pos位置之前插入x
    22. void LTErase(LTNode* pos);//在pos位置删除
    23. void LTDestroy(LTNode* phead);

    2.带头双向链表的实现

    2.1创建新节点

    创建一个节点比较简单,首先malloc一块空间出来,然后将这个结构体的数据data设置成需要的数据,再将前驱指针和后驱指针全部置空就行,方便后续链接。

    1. LTNode* BuyLTNode(LTDataType x)
    2. {
    3. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    4. if (node == NULL)
    5. {
    6. perror("malloc fail");
    7. exit(-1);
    8. }
    9. node->data = x;
    10. node->next = NULL;
    11. node->prev = NULL;
    12. return node;
    13. }

    2.2链表初始化

    双向带头链表的初始化肯定需要一个头节点,所以使用BuyLTNode创建一个头节点phead,然后将phead的next指向自己,prev也指向自己。

    1. LTNode* LTInit()
    2. {
    3. LTNode* phead = BuyLTNode(0);
    4. phead->next = phead;
    5. phead->prev = phead;
    6. return phead;
    7. }

    2.3打印链表

    打印链表也很简单,只需要创建一个结构体指针cur来遍历链表就可以了,循环的结束条件是cur为phead,为什么呢?因为尾节点的next不为NULL,而是头指针,如果条件设置为NULL的话,就会陷入死循环,所以当cur走到头节点的话就代表已经遍历完一遍了。

    1. void LTPrint(LTNode* phead)
    2. {
    3. LTNode* cur = phead->next;
    4. printf("phead");
    5. while (cur != phead)
    6. {
    7. printf("<->%d", cur->data);
    8. cur = cur->next;
    9. }
    10. printf("\n");
    11. }

    2.4尾插

    假设当前节点是这样的情况,我们要插入一个newnode,怎么尾插呢?这个时候我们就通过头节点phead找到尾节点n3,然后再尾插就可以了。首先定义一个结构体指针tail来找n3,然后将newnode进行链接,首先将newnode与tail进行链接,newnode->prev=tail,tail->next=newnode,再将newnode与phead进行链接,phead->prev=newnode,newnode->next=phead,然后newnode就在这个链表上完成尾插了。

     

    1. void LTPushBack(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. LTNode* tail = phead->prev;
    5. LTNode* newnode = BuyLTNode(x);
    6. newnode->prev = tail;
    7. tail->next = newnode;
    8. newnode->next = phead;
    9. phead->prev = newnode;
    10. }

    2.5尾删

    尾删的话不仅要找到尾节点,还需要找到尾节点的前一个节点,所以创建一个尾节点tail,尾节点的前一个节点tailprev(tailprev=tail->prev),然后将tailprev与头节点进行链接,phead->prev=tailprev,tailprev->next=phead。

    1. void LTPopBack(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next != phead);
    5. LTNode* tail = phead->prev;
    6. LTNode* pretail = tail->prev;
    7. phead->prev = pretail;
    8. pretail->next = phead;
    9. }

    2.6头插

    头插需要找到第一个节点,所以创建一个结构体指针tail指向第一个节点(tail=phead->next),然后将newnode与phead进行链接,phead->nedxt=newnode,newndoe->prev=phead,再将newnode与tail进行链接,newndoe->next=tail,tail->prev=newnode.

    1. void LTPushFront(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. LTNode* newnode = BuyLTNode(x);
    5. LTNode* tail = phead->next;
    6. phead->next = newnode;
    7. newnode->prev = phead;
    8. newnode->next = tail;
    9. tail->prev = newnode;
    10. }

    2.7头删

    头删需要找到第一个和第二个节点,所以定义一个结构体指针first来指向第一个节点,second指向第二个节点(second=first->next),然后将第二个节点和phead进行链接,phead->next=second,second->prev=phead,然后释放第一个节点的空间,free(first)。

    1. void LTPopFront(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next!=phead);
    5. LTNode* first = phead->next;
    6. LTNode* second = first->next;
    7. phead->next =second;
    8. second->prev = phead;
    9. free(first);
    10. }

    2.8有效数据

    求出链表的有效数据个数就比较简单了,遍历的时候size++就行了。

    1. int LTSize(LTNode* phead)
    2. {
    3. assert(phead);
    4. int size = 0;
    5. LTNode* cur = phead->next;
    6. while (cur != phead)
    7. {
    8. size++;
    9. cur = cur->next;
    10. }
    11. return size;
    12. printf("\n");
    13. }

    2.9寻找节点

    寻找节点也比较简单,在遍历链表的时候判断当前节点的data是否等于x即可。

    1. LTNode* LTFind(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. LTNode* cur = phead->next;
    5. while (cur != phead)
    6. {
    7. if (cur->data = x)
    8. {
    9. return cur;
    10. }
    11. cur = cur->next;
    12. }
    13. return NULL;
    14. }

    2.10在pos位置之前插入x

    这里需找到pos和pos前一个节点,所以创建一个结构体指针posprev指向pos的前一个节点(posprev=pos->prev),然后将newnode与posprev进行链接,posprev->next=newnode,newnoe->prev=posprev,再将newnode与pos进行链接,pos->prev=newnode,newnode->next=pos。

    1. void LTInsert(LTNode* pos, LTDataType x)
    2. {
    3. assert(pos);
    4. LTNode* newnode = BuyLTNode(x);
    5. LTNode* prepos = pos->prev;
    6. newnode->next = pos;
    7. pos->prev = newnode;
    8. newnode->prev = prepos;
    9. prepos->next = newnode;
    10. }

    将这个函数完成之后就可以对头插与尾插进行简化了。

    尾插:

    这里大家需要了解一下为什么第一个参数是phead,因为LTInsert这个函数是实现pos位置之前的插入,当我们需要尾插,那么尾巴的后一个节点是什么呢?不就是头节点吗!!

    1. void LTPushBack(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. LTInsert(phead, x);
    5. }

    头插:

    头插就比较好理解了,第一个参数就是头节点的下一个节点。

    1. void LTPushFront(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. LTInsert(phead->next, x);
    5. }

    2.11删除pos位置的节点

    删除pos位置需要找到pos的前一个节点和后一个节点,所以创建一个结构体指针posprev保存pos的前一个节点(posprev=pos->prev),再创建一个结构体指针保存pos的后一个节点(posnext=pos->next),然后将posprev与posnext进行链接,posprev->next=posnext,posnext->prev=posprev,然后free掉pos。

    1. void LTErase(LTNode* pos)
    2. {
    3. assert(pos);
    4. LTNode* posNext = pos->next;
    5. LTNode* posPrev = pos->prev;
    6. free(pos);
    7. posNext->prev = posPrev;
    8. posPrev->next = posNext;
    9. }

    将这个函数完成之后就可以对头删与尾删进行简化了。

    头删:

    1. void LTPopFront(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next!=phead);
    5. LTErase(phead->next);
    6. }

    尾删:

    尾节点通过头节点phead的前驱指针来找就好了。

    1. void LTPopBack(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next != phead);
    5. LTErase(phead->prev);
    6. }

    2.12销毁

    销毁链表还是和单链表一样,区别就是循环结束条件是cur走到头节点,还有就是保存cur的后一个节点,最后还要free掉头节点,因为没有走到头节点。

    1. void LTDestroy(LTNode* phead)
    2. {
    3. assert(phead);
    4. LTNode* cur = phead->next;
    5. while (cur != phead)
    6. {
    7. LTNode* next = cur->next;
    8. free(cur);
    9. cur = next;
    10. }
    11. free(phead);
    12. }

    今天的分享到这里就结束啦!谢谢老铁们的阅读,让我们下期再见。

  • 相关阅读:
    EdgeSAM: Prompt-In-the-Loop Distillation for On-Device Deployment of SAM
    Spring Boot 3.0:构建下一代Java应用的新方法
    物联网各类数据如何轻松获取?秘诀就在定制文件推送服务
    【C 语言进阶(12)】动态内存管理笔试题
    Spire.Doc 10.11.9 支持设置形状填充颜色的透明度
    8年测试经验,简单易懂的讲解一下什么是自动化测试?
    山海鲸报表系统:数据洞察的利器
    SCI论文从投稿到录用的完整处理流程-经验总结
    Vue开发中Jwt的使用
    网站权重查询易语言代码
  • 原文地址:https://blog.csdn.net/2301_79035870/article/details/134444149