详解带头双向循环列表

简介: 详解带头双向循环列表

前言


上篇文章我们讲述了链表,链表有带头、不带头,循环、不循环,单向、双向,三种大的形式这三种形式可以组合出8中结构,今天我们学习最常用的其中一种——带头双向循环链表。


一、带头双向循环链表的结构


6bf3493ac86a4221bdabc6349d4430e9.png


我们可以看出这个结构和上篇文章的无头单向非循环链表最大的区别是含有一个头部和每个结构内都含有两个指针,一个指向下个结构,一个指向前一个结构。


二、 带头双向循环链表的实现


2.1链表的创建


typedef int SLTDatatype;
typedef struct SListNode
{
  SLTDatatype data;
  struct SListNode* next;
  struct SListNode* prev;
}SLTNode;


从这个结构体我们就可以看到里面含有两个指针一个next指向下一个,一个prev指向前一个。


2.2开辟新的结点


SLTNode* BuySLT( SLTDatatype x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode==NULL)
  {
    perror("malloc failed");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
}


2.3初始化

SLTNode* SLTInit()
{
  SLTNode* phead = BuySLT(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}


这个初始化会创建一个头,我们这个头是循环的,要进行的增删查改的操作也是以这个头为根基进行操作的。


2.4释放销毁


void SLTDer(SLTNode* phead)
{
  SLTNode* cur = phead->next;
  while (cur != phead)
  {
    SLTNode* next = cur->next;
    free(cur);
    cur = next;
  }
  free(phead);
}


我们要使用malloc函数开辟空间,在程序结束时也要依次释放,否则会造成内存泄漏。


2.5链表的打印


void SLprint(SLTNode*phead)
{
  SLTNode* tail = phead->next;
  printf("phead<=>");
  while (tail!= phead)
  {
    printf("%d<=>", tail->data);
    tail = tail->next;
  }
  printf("\n");
}


2.6查找链表中的数据

SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
  assert(phead);
  SLTNode* cur = phead->next;
  while (cur!= phead)
  {
    if (cur->data == x)
    {
      return cur;
    }
    else
    {
      cur = cur->next;
    }
  }
  return NULL;
}

由于我们的链表是循环的不能以结尾指向空的指针结尾,要根据这个结构以不指向头的指针为判断条件创建循环,依次打印每个结构中的有效数据。


2.7尾插


void SLPushBack(SLTNode* phead, SLTDatatype x)
{
  assert(phead);
  SLTNode* newnode = BuySLT(x);
  SLTNode* tail = phead->prev;
  newnode->prev = tail;
  tail->next = newnode;
  newnode->next = phead;
  phead->prev = newnode;
}

根据这个链表的结构特点,我们可以通过头指针里的指向后一个也就是末尾的结构体,然后将末尾的结构和新结点里的两个指针连接起来,再将新节点和头结点链接起来,尾插就完成了,整个结构也就串联起来了。


2.8尾删


void SLPopBack(SLTNode* phead)
{
  assert(phead);
  if (phead->next != phead)
  {
    SLTNode* tail = phead->prev;
    SLTNode* first = tail->prev;
    first->next = phead;
    phead->prev = first;
    free(tail);
  }
}

通过头指针里的指向后一个指针就可以找到链表的尾,再根据链表的尾找到前一个保存前一个释放掉尾,然后再将其和头串联起来。


2.9头插


void SLPushFront(SLTNode* phead,SLTDatatype x)
{
  assert(phead);
  SLTNode* newnode = BuySLT(x);
  SLTNode* first = phead->next;
  newnode->next = first;
  first->prev = newnode;
  phead->next = newnode;
  newnode->prev = phead;
}

还是根据这个链表的结构特点就很容易完成头插的操作


2.10头删


void SLPopFront(SLTNode* phead)
{
  assert(phead);
  SLTNode* first = phead->next;
  SLTNode* cur = first->next;
  cur->prev = phead;
  phead->next = cur;
  free(first);
}

和前面的操作大差不差根据这个链表的特点完成这个操作


三、带头双向循环链表中间随机值的插入和删除


这个附加内容就不详解了,通过前面的文章大家应该能有自己的思路了吧!给大家个参考。


3.1在pos位置插入x


void SLTInsert( SLTNode* pos, SLTDatatype x)
{
  assert(pos);
  SLTNode* posprev = pos->prev;
  SLTNode* newnode = BuySLT(x);
  posprev->next = newnode;
  newnode->prev = posprev;
  newnode->next = pos;
  pos->prev = newnode;
}


3.2删除pos位置的值


void SLTErase(SLTNode* pos)
{
  assert(pos);
  SLTNode* first = pos->prev;
  SLTNode* cur = pos->next;
  first->next = cur;
  cur->prev = first;
  free(pos);
}


四、完整代码


#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDatatype;
typedef struct SListNode
{
  SLTDatatype data;
  struct SListNode* next;
  struct SListNode* prev;
}SLTNode;
SLTNode* BuySLT( SLTDatatype x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode==NULL)
  {
    perror("malloc failed");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
}
void SLTDer(SLTNode* phead)
{
  SLTNode* cur = phead->next;
  while (cur != phead)
  {
    SLTNode* next = cur->next;
    free(cur);
    cur = next;
  }
  free(phead);
}
SLTNode* SLTInit()
{
  SLTNode* phead = BuySLT(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}
void SLprint(SLTNode*phead)
{
  SLTNode* tail = phead->next;
  printf("phead<=>");
  while (tail!= phead)
  {
    printf("%d<=>", tail->data);
    tail = tail->next;
  }
  printf("\n");
}
SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
  assert(phead);
  SLTNode* cur = phead->next;
  while (cur!= phead)
  {
    if (cur->data == x)
    {
      return cur;
    }
    else
    {
      cur = cur->next;
    }
  }
  return NULL;
}
//尾插
void SLPushBack(SLTNode* phead, SLTDatatype x)
{
  assert(phead);
  SLTNode* newnode = BuySLT(x);
  SLTNode* tail = phead->prev;
  newnode->prev = tail;
  tail->next = newnode;
  newnode->next = phead;
  phead->prev = newnode;
}
//尾删
void SLPopBack(SLTNode* phead)
{
  assert(phead);
  if (phead->next != phead)
  {
    SLTNode* tail = phead->prev;
    SLTNode* first = tail->prev;
    first->next = phead;
    phead->prev = first;
    free(tail);
  }
}
//头插
void SLPushFront(SLTNode* phead,SLTDatatype x)
{
  assert(phead);
  SLTNode* newnode = BuySLT(x);
  SLTNode* first = phead->next;
  newnode->next = first;
  first->prev = newnode;
  phead->next = newnode;
  newnode->prev = phead;
}
//头删
void SLPopFront(SLTNode* phead)
{
  assert(phead);
  SLTNode* first = phead->next;
  SLTNode* cur = first->next;
  cur->prev = phead;
  phead->next = cur;
  free(first);
}
//
void SLTInsert( SLTNode* pos, SLTDatatype x)
{
  assert(pos);
  SLTNode* posprev = pos->prev;
  SLTNode* newnode = BuySLT(x);
  posprev->next = newnode;
  newnode->prev = posprev;
  newnode->next = pos;
  pos->prev = newnode;
}
//
void SLTErase(SLTNode* pos)
{
  assert(pos);
  SLTNode* first = pos->prev;
  SLTNode* cur = pos->next;
  first->next = cur;
  cur->prev = first;
  free(pos);
}
int main()
{
  SLTNode* plist = SLTInit();
  //尾插
  SLPushBack(plist, 1);
  SLPushBack(plist, 2);
  SLPushBack(plist, 3);
  SLPushBack(plist, 4);
  SLPushBack(plist, 5);
  //尾插测试
  printf("尾插:\n");
  SLprint(plist);
  //尾删
  SLPopBack(plist);
  printf("尾删:\n");
  SLprint(plist);
  //头插
  SLPushFront(plist,10);
  printf("头插:\n");
  SLprint(plist);
  //头删
  SLPopFront(plist);
  printf("头删:\n");
  SLprint(plist);
  //在pos位置后插入x
  SLTNode* pos1 = SLFind(plist, 4);
  SLTInsert(pos1, 5);
  printf("在pos位置插入x:\n");
  SLprint(plist);
  //删除pos位置的值
  SLTNode* pos2 = SLFind(plist, 5);
  SLTErase(pos2);
  printf("删除pos位置的值\n");
  SLprint(plist);
  SLTDer(plist);
  plist = NULL;
  return 0;
}


cf4dd0a1b6dd44e7a33be8685c33e2e7.png

这个就是本篇文章的所有内容了,通读全文我们可以发现带头双向循环链表这个结构确实实用,使用起来也是非常的方便简洁,数据结构链表的内容到这就基本结束了,欢迎大家在评论区留言,交流和探讨,说出自己的想法。

相关文章
|
3月前
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
91 4
|
4月前
|
存储
链表的遍历方式
链表的遍历方式
|
6月前
|
存储
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
【单链表】无头单项不循环(1)
【单链表】无头单项不循环(1)
608 2
【单链表】无头单项不循环(2)
【单链表】无头单项不循环(2)
624 2
|
存储 算法
单向循环链
单向循环链表是一种链式存储结构,每个节点只包含一个指针,指向其后继。相比于单向链表,单向循环链表在插入和删除节点时需要移动元素的指针,因此时间复杂度较高。但是,单向循环链表的内存占用较少,且在某些情况下可以减少内存碎片。
75 7
|
存储 C语言
单链表(无头单项非循环)
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。链表的形式有很多,本篇文章主要介绍的是单链表且无头结点。在严版数据结构(C语言 第2版)中,单链表采用的是有头节点,这两种形式,各有利弊。含头节点的单链表在学习时,可能会容易些,但是在实践中或者在力扣中做题时,很少会有带头节点。但是有时候做题,使用带头节点的单链表会简单许多,不常见。
72 0
单链表(无头单项非循环)
|
11月前
|
存储 算法
数据结构单链表之检测和删除链表中的循环 | 第十三套
数据结构单链表之检测和删除链表中的循环 | 第十三套
50 0
|
存储 C++
【双向链表】带头双向循环(1)
【双向链表】带头双向循环(1)
63 0
|
存储 移动开发 算法
【数据结构和算法】使用数组的结构实现链表(单向或双向)
【数据结构和算法】使用数组的结构实现链表(单向或双向)