详解双向循环带头节点链表——十分钟单手吊打链表

简介: 和单链表比较🤔之前我讲过了单链表,也就是单向不带头不循环链表,看起来和现在的这个简直天差地别,但是——没有关系,我们必须知道一点:

和单链表比较🤔

之前我讲过了单链表,也就是单向不带头不循环链表,看起来和现在的这个简直天差地别,但是——没有关系,我们必须知道一点:


单向不带头不循环链表是最简单的结构,但实现却比较复杂

双向循环带头节点链表是最复杂的结构,但实现却最简单


单链表一般出现在我们熟知的 OJ 题目中,而生活中实际运用却高度依赖于双链表,因为双链表效率高,实现方便,简单易懂,代码清爽,结构严谨,巴拉巴拉……


分类🤔

之前提到过三个分类:

image.png

循环就是指链表尾节点不指向 NULL 而是指向 phead,从而使头插头删尾插尾删等基本操作变得十分简单。

当然不止以上6种,两两排列组合可以得到更多的形式。

空间申请👏

老规矩,开辟空间肯定是第一步:

Seqlist* buymalloc(Seqtype x)
{
  Seqlist* newnode = (Seqlist*) malloc (sizeof(Seqlist));
  if (newnode == NULL)
  {
    printf("malloc fail!\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
}

初始化👏


Seqlist* phead = NULL;
  phead=ListInit();

这里就开始设置哨兵位头节点:


Seqlist* init()
{
  Seqlist* phead = buymalloc(0);
  phead->next = phead;//指向下一个节点的指针指向自身
  phead->prev = phead;//指向上一个节点的指针指向自身
  return phead;
}

因为创建的结构体指针 phead 指向NULL,因此我们需要在初始化时创建一个哨兵位头结点,使 phead 指向这个头节点;在后续的使用中,plist始终是指向这个头结点,并不会被修改,因此不需要传入二级指针


存在哨兵位头结点时,改变的是哨兵位(结构体)节点,传入的是结构体的指针(地址),非哨兵位改变的是结构体指针那传入的就是结构体指针的指针(地址)


注意:这里开辟空间参数为 0,这里目的是只要让头结点不为空即可,不用在意值的大小。


尾插👏

以基本功能举两个栗子来实际体会一下和单链表的差距:

void pushback(Seqlist* phead, Seqtype x)
{
  assert(phead);
  Seqlist* newnode = buymalloc(x);
  Seqlist* tail = phead->prev;
  tail->next = newnode;
  tail = newnode->prev;
  newnode->next = phead;
  phead->prev = newnode;
}

image.png

void popback(Seqlist* phead)
{
  assert(phead);
  assert(phead != phead->next);
  Seqlist* tail = phead->prev;
  Seqlist* tailprev = tail->prev;
  free(tail);
  tail = NULL;
  tailprev->next = phead;
  phead->prev = tailprev;
}

image.png

pos位插入👏

其实比起头插尾插,pos位插入其实更为简单,注意pos位插入是指 pos 的前一位插入,思路即 pos 前前一个节点与pos前一个节点链接,再和 pos 后一个节点链接就行,中间步骤直接链接即可。

void insert(Seqlist* pos, Seqtype x)
{
  assert(pos);
  Seqlist* newnode = buymalloc(x);
  Seqlist* posprev = pos->prev;//找到插入节点的正确位置
  newnode->next = pos;
  posprev->next = newnode;
  newnode->prev = posprev;
}

我们之前说过,头插头删尾插尾删其实是 pos 位插入删除的特殊实现情况,所以我们格局打开:写出 pos 位插入删除即可搞定所有插删操作。

那么头插就可以写成这样:

image.png

😎是不是突然就觉得头尾删插是个什么渣渣!😎

没错,这就是结构最复杂操作却最简单的双向带头循环链表。

源码奉上🎉

Slist.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int Seqtype;
typedef  struct Seqlist
{
  Seqtype data;
  struct Seqlist* next;
  struct Seqlist* prev;
}Seqlist;
Seqlist* buymalloc(Seqtype x);//开辟空间
Seqlist* init();//初始化
void print(Seqlist* phead);//打印
void pushback(Seqlist* phead, Seqtype x);//尾插
void popback(Seqlist* phead);//尾删
void popfront(Seqlist* phead);//头删
void pushfront(Seqlist* phead, Seqtype x);//头插
Seqlist* find(Seqlist* phead, Seqtype x);//查找
void insert(Seqlist* pos, Seqtype x);//pos位插入
void erase(Seqlist* pos);//pos位删除

test.c


# define _CRT_SECURE_NO_WARNINGS 
#include"Slist.h"
void test()
{
  Seqlist* phead = init();
  pushback(phead, 2);
  pushback(phead, 3);
  popback(phead);
    print(phead);
}
int main()
{
  test();
  return 0;
}

Slist.c


# define _CRT_SECURE_NO_WARNINGS 
#include"Slist.h"
Seqlist* buymalloc(Seqtype x)
{
  Seqlist* newnode = (Seqlist*) malloc (sizeof(Seqlist));
  if (newnode == NULL)
  {
    printf("fail!\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
}
void pushback(Seqlist* phead, Seqtype x)
{
  assert(phead);
  Seqlist* newnode = buymalloc(x);
  Seqlist* tail = phead->prev;
  tail->next = newnode;
  tail = newnode->prev;
  newnode->next = phead;
  phead->prev = newnode;
}
//哨兵位头结点,改变的是哨兵位(结构体)节点,
//传的是结构体的指针(地址),非哨兵位改变的是结构体指针那
//传的就是结构体指针的指针(地址)
void print(Seqlist* phead)
{
  assert(phead);
  Seqlist* newnode = phead->next;
  while(newnode != phead)
  {
    printf("%d ", newnode->data);
    newnode = newnode->next;
  }
}
Seqlist* init()
{
  Seqlist* phead = buymalloc(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}
void popback(Seqlist* phead)
{
  assert(phead);
  assert(phead != phead->next);
  Seqlist* tail = phead->prev;
  Seqlist* tailprev = tail->prev;
  free(tail);
  tail = NULL;
  tailprev->next = phead;
  phead->prev = tailprev;
}
void insert(Seqlist* pos, Seqtype x)
{
  assert(pos);
  Seqlist* newnode = buymalloc(x);
  Seqlist* posprev = pos->prev;
  newnode->next = pos;
  posprev->next = newnode;
  newnode->prev = posprev;
}
void pushfront(Seqlist* phead, Seqtype x)
{
  assert(phead);
  insert(phead->next, x);
}
Seqlist* find(Seqlist* phead, Seqtype x)
{
  assert(phead);
  Seqlist* cur = phead->next;
  while (cur != phead)
  {
    if (cur->data == x)
    {
      return cur;
    }
    cur = cur->next;
  }
  return NULL;
}


相关文章
|
1月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
18 0
LeetCode第二十四题(两两交换链表中的节点)
|
1月前
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
44 0
Leetcode第十九题(删除链表的倒数第N个节点)
|
1月前
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
49 0
|
3月前
|
算法
LeetCode第24题两两交换链表中的节点
这篇文章介绍了LeetCode第24题"两两交换链表中的节点"的解题方法,通过使用虚拟节点和前驱节点技巧,实现了链表中相邻节点的交换。
LeetCode第24题两两交换链表中的节点
|
2月前
|
存储 算法 C语言
C语言手撕实战代码_循环单链表和循环双链表
本文档详细介绍了用C语言实现循环单链表和循环双链表的相关算法。包括循环单链表的建立、逆转、左移、拆分及合并等操作;以及双链表的建立、遍历、排序和循环双链表的重组。通过具体示例和代码片段,展示了每种算法的实现思路与步骤,帮助读者深入理解并掌握这些数据结构的基本操作方法。
05_删除链表的倒数第N个节点
05_删除链表的倒数第N个节点
04_两两交换链表中的节点
04_两两交换链表中的节点
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
54 5
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 18. 删除链表的节点
Leetcode题目"剑指 Offer 18. 删除链表的节点"的Python解决方案,通过使用双指针法找到并删除链表中值为特定数值的节点,然后返回更新后的链表头节点。
41 4
|
4月前
|
安全 云计算
云计算自旋锁问题之在线程安全地删除链表节点时,需要频繁加锁会影响性能如何解决
云计算自旋锁问题之在线程安全地删除链表节点时,需要频繁加锁会影响性能如何解决
51 2