详解带头双向循环列表

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

前言


上篇文章我们讲述了链表,链表有带头、不带头,循环、不循环,单向、双向,三种大的形式这三种形式可以组合出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

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

相关文章
|
存储 关系型数据库 MySQL
MySQL MVCC全面解读:掌握并发控制的核心机制
【10月更文挑战第15天】 在数据库管理系统中,MySQL的InnoDB存储引擎采用了一种称为MVCC(Multi-Version Concurrency Control,多版本并发控制)的技术来处理事务的并发访问。MVCC不仅提高了数据库的并发性能,还保证了事务的隔离性。本文将深入探讨MySQL中的MVCC机制,为你在面试中遇到的相关问题提供全面的解答。
1031 2
|
机器学习/深度学习 自然语言处理 算法
《深度解析:全连接层—卷积神经网络中的关键纽带》
全连接层在卷积神经网络(CNN)中起着桥梁作用,将卷积层和池化层提取的局部特征整合为全局特征,实现分类或回归任务。每个神经元与前一层所有神经元相连,通过权重和偏置进行特征转换,并引入激活函数以增强非线性建模能力。尽管参数量大易导致过拟合,但可通过正则化、Dropout和批标准化等技术有效应对,从而提升模型性能。
1441 8
|
Python
Matplotlib 安装
Matplotlib 安装
408 3
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
1284 0
|
传感器 人工智能 监控
未来出行的革新:智能交通系统的崛起
【10月更文挑战第9天】 智能交通系统(ITS)正在改变我们未来的出行方式。本文深入探讨了ITS的技术原理、关键组成部分以及其在不同领域的实际应用,并讨论了面临的挑战及未来发展的前景。通过阐述这些内容,本文揭示了智能交通系统在提升交通效率、安全性和可持续性方面的巨大潜力。
|
数据采集 存储 数据可视化
数据清洗
数据清洗
966 2
WK
|
机器学习/深度学习 算法
为什么Sigmoid函数比Tanh函数更好
在神经网络中,Sigmoid和Tanh函数各有优劣,选择取决于具体应用场景。Sigmoid函数输出范围为(0,1),适合二分类问题,但存在梯度消失和非零中心化的问题;Tanh函数输出范围为(-1,1),以0为中心,有利于加速收敛,但同样涉及较大的计算复杂度。两者均存在梯度消失风险,但在多数情况下,Tanh梯度问题较轻。随着技术发展,ReLU等新型激活函数因能有效缓解梯度消失并提高计算效率,已成为许多任务的首选。因此,不能简单地说Sigmoid比Tanh更好,需依据任务需求和网络结构进行选择。
WK
1217 1
|
机器学习/深度学习 算法 Unix
循环编码:时间序列中周期性特征的一种常用编码方式
循环编码是深度学习中处理周期性数据的一种技术,常用于时间序列预测。它将周期性特征(如小时、日、月)转换为网络可理解的形式,帮助模型识别周期性变化。传统的one-hot编码将时间特征转换为分类特征,而循环编码利用正弦和余弦转换,保持时间顺序信息。通过将时间戳转换为弧度并应用sin和cos,每个原始特征只映射到两个新特征,减少了特征数量。这种方法在神经网络中有效,但在树模型中可能需谨慎使用。
2120 5
|
机器学习/深度学习 存储 自然语言处理
RNN与LSTM:循环神经网络的深入理解
【6月更文挑战第14天】本文深入探讨RNN和LSTM,两种关键的深度学习模型在处理序列数据时的作用。RNN利用记忆单元捕捉时间依赖性,但面临梯度消失和爆炸问题。为解决此问题,LSTM引入门控机制,有效捕获长期依赖,适用于长序列处理。RNN与LSTM相互关联,LSTM可视为RNN的优化版本。两者在NLP、语音识别等领域有广泛影响,未来潜力无限。