当前位置:   article > 正文

线性表之带头双向循环链表_设以带头结点的双向循环链表表示的线性表

设以带头结点的双向循环链表表示的线性表

目录

一、本章重点

二、带头双向循环链表介绍

2.1什么是带头双向循环链表?

 2.2最常用的两种链表结构

三、带头双向循环链表常用接口实现 

3.1结构体创建

四、实现接口总结

 五、在线oj训练与详解


一、本章重点

  1. 带头双向循环链表介绍
  2. 带头双向循环链表常用接口实现
  3. 实现接口总结
  4. 在线oj训练与详解

二、带头双向循环链表介绍

2.1什么是带头双向循环链表?

  • 带头:存在一个哨兵位的头节点,该节点是个无效节点,不存储任何有效信息,但使用它可以方便我们头尾插和头尾删时不用判断头节点指向NULL的情况,同时也不需要改变头指针的指向,也就不需要传二级指针了。 
  • 双向:每个结构体有两个指针,分别指向前一个结构体和后一个结构体。
  • 循环:最后一个结构体的指针不再指向NULL,而是指向第一个结构体。(单向)
  • 第一个结构体的前指针指向最后一个结构体,最后一个结构体的后指针指向第一个结构体(双向)。

图解 

 2.2最常用的两种链表结构

  • 更具有无头,单双向,是否循环组合起来有8种结构,但最长用的还是无头单向非循环链表和带头双向循环链表
  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  •  带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

三、带头双向循环链表常用接口实现 

3.1结构体创建

  1. typedef int DataType;
  2. typedef struct DListNode
  3. {
  4. DataType data;
  5. DListNode* prev;
  6. DListNode* next;
  7. }DListNode;

3.2带头双向循环链表的初始化 

  1. void DListInint(DListNode** pphead)
  2. {
  3. *pphead = (DListNode*)malloc(sizeof(DListNode));
  4. (*pphead)->next = (*pphead);
  5. (*pphead)->prev = (*pphead);
  6. }

 或者使用返回节点的方法也能实现初始化

  1. DListNode* DListInit()
  2. {
  3. DListNode* phead = (DListNode*)malloc(sizeof(DListNode));
  4. phead->next = phead;
  5. phead->prev = phead;
  6. return phead;
  7. }

 3.3创建新节点

  1. DListNode* BuyDListNode(DataType x)
  2. {
  3. DListNode* temp = (DListNode*)malloc(sizeof(DListNode));
  4. if (temp == NULL)
  5. {
  6. printf("malloc fail\n");
  7. exit(-1);
  8. }
  9. temp->prev = NULL;
  10. temp->next = NULL;
  11. temp->data = x;
  12. return temp;
  13. }

  3.4尾插

  1. void DListPushBack(DListNode* phead,DataType x)
  2. {
  3. DListNode* newnode = BuyDListNode(x);
  4. DListNode* tail = phead->prev;
  5. tail->next = newnode;
  6. newnode->prev = tail;
  7. newnode->next = phead;
  8. phead->prev = newnode;
  9. }

   3.5打印链表

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

    3.6头插

  1. void DListNodePushFront(DListNode* phead, DataType x)
  2. {
  3. DListNode* next = phead->next;
  4. DListNode* newnode = BuyDListNode(x);
  5. next->prev = newnode;
  6. newnode->next = next;
  7. newnode->prev = phead;
  8. phead->next = newnode;
  9. }

    3.7尾删

  1. void DListNodePopBack(DListNode* phead)
  2. {
  3. if (phead->next == phead)
  4. {
  5. return;
  6. }
  7. DListNode* tail = phead->prev;
  8. DListNode* prev = tail->prev;
  9. prev->next = phead;
  10. phead->prev = prev;
  11. free(tail);
  12. tail = NULL;
  13. }

 3.8头删

  1. void DListNodePopFront(DListNode* phead)
  2. {
  3. if (phead->next == phead)
  4. {
  5. return;
  6. }
  7. DListNode* firstnode = phead->next;
  8. DListNode* secondnode = firstnode->next;
  9. secondnode->prev = phead;
  10. phead->next = secondnode;
  11. free(firstnode);
  12. firstnode = NULL;
  13. }

  3.9查找data(返回data的节点地址)

  1. DListNode* DListNodeFind(DListNode* phead, DataType x)
  2. {
  3. DListNode* firstnode = phead->next;
  4. while (firstnode != phead)
  5. {
  6. if (firstnode->data == x)
  7. {
  8. return firstnode;
  9. }
  10. firstnode = firstnode->next;
  11. }
  12. return NULL;
  13. }

    3.10在pos位置之前插入节点

  1. void DListNodeInsert(DListNode* pos, DataType x)
  2. {
  3. DListNode* prev = pos->prev;
  4. DListNode* newnode = BuyDListNode(x);
  5. newnode->next = pos;
  6. newnode->prev = prev;
  7. prev->next = newnode;
  8. pos->prev = newnode;
  9. }

3.11删除pos位置的节点

  1. void DListNodeErase(DListNode* pos)
  2. {
  3. DListNode* prev = pos->prev;
  4. DListNode* next = pos->next;
  5. prev->next = next;
  6. next->prev = prev;
  7. free(pos);
  8. pos = NULL;
  9. }

四、实现接口总结

  1. 多画图能给清晰展示变化的过程,有利于实现编程。
  2. 小知识head->next既可表示前一个结构体的成员变量,有可表示后一个结构体的地址。当head->next作为左值时代表的是成员变量,作右值时代表的是后一个结构体的地址。对于链表来说理解这一点非常重要。
  3. 实践实践出真知
  4. 带头双向循环链表相比于单链表,它实现起来更简单,不用向单链表一样分情况讨论链表的长度。虽然结构较复杂,但使用起来更简单,更方便。

 五、在线oj训练与详解

5.1链表的中间节点力扣

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

来源:力扣(LeetCode)

 思路:快慢指针

取两个指针,初始时均指向head,一个为快指针(fast)一次走两步,另一个为慢指针(slow)一次走一步,当快指针满足fast==NULL(偶数个节点)或者fast->next==NULL(奇数个节点)时,slow指向中间节点,返回slow即可。

  1. struct ListNode* middleNode(struct ListNode* head)
  2. {
  3. struct ListNode* fast=head;
  4. struct ListNode* slow=head;
  5. while(fast&&fast->next)
  6. {
  7. fast=fast->next->next;
  8. slow=slow->next;
  9. }
  10. return slow;
  11. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/835059
推荐阅读
相关标签
  

闽ICP备14008679号