【数据结构】双向链表

简介: 【数据结构】双向链表



🗒️前言:

在上一期中我们介绍了单链表,也做了一些练习题,在一些题中使用单链表会十分繁琐。因为单链表只能正着走,不能倒着走,例如:回文、逆置。本期我们将学习带头双向循环链表。

一、双向链表

1.1带头双向循环链表的结构

特点:带头双向循环链表结构最复杂,一般用在单独存储数据。结构虽然结构复杂,但是使用代码实现以后会发现结构会带来多优势,实现反而简单了。

二、链表的实现

📒2.1初始化

LTNode* BuyLTNode(LTDateType x)
{
  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  if (newnode == NULL)
  {
    perror("malloc");
    exit(-1);
  }
  newnode->date = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
}
LTNode* LTInit()
{
  LTNode* phead = BuyLTNode(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}

📒2.2尾插

带哨兵位的链表尾插时不用判断是否有节点,两种情况的插入相同,而且还不用传递二级指针。

void LTPushBack(LTNode* phead, LTDateType x)
{
  assert(phead);
  
  LTNode* tail = phead->prev;
  LTNode* newnode = BuyLTNode(x);
  newnode->prev = tail;
  tail->next = newnode;
  newnode->next = phead;
  phead->prev = newnode;
}

📒2.3尾删

在尾删时我们通过 assert(phead->next != phead);  判断链表是否有节点。同时这个代码就有普遍性,不用单独考虑剩一个节点的情况。

void LTPopBack(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);
  LTNode* tail = phead->prev;
  LTNode* prevtail = tail->prev;
  free(tail);
  prevtail->next = phead;
  phead->prev = prevtail;
}

📒2.4头插

头删重要的是赋值的顺序,顺序错误会找不到后面的节点,导致内存泄漏。带哨兵位的链表不需要传递二级指针,因为改变的是结构体的变量。

void LTPushFront(LTNode* phead, LTDateType x)
{
  assert(phead);
  LTNode* newnode = BuyLTNode(x);
  
  newnode->next = phead->next;
  phead->next->prev = newnode;
  phead->next = newnode;
  newnode->prev = phead;
}

📒2.5头删

我们可以多定义几个指针来保存后面节点的地址,这样就不会造成节点的丢失,不用考虑赋值的顺序,会更加方便。

void LTPopFront(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);
  LTNode* first = phead->next;
  LTNode* second = first->next;
  free(first);
  phead->next = second;
  second->prev = phead;
}

📒 2.6在pos位置之前插入        

void LTInsert(LTNode* pos, LTDateType x)
{
  assert(pos);
  LTNode* newnode = BuyLTNode(x);
  LTNode* posprev = pos->prev;
  newnode->next = pos;
  pos->prev = newnode;
  newnode->prev = posprev;
  posprev->next = newnode;
}

我们可以通过复用LTInsert,实现链表的头插和尾插,使程序更简单。

  • 当pos传递的是phead->next,就可以实现头插
  • 当pos传递的是phead,就可以实现尾插
//头插
void LTPushFront(LTNode* phead, LTDateType x)
{
  assert(phead);
  
  LTInsert(phead->next, x);
}
//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
  assert(phead);
  LTInsert(phead, x);
}

📒 2.7删除pos位置

void LTErase(LTNode* pos)
{
  assert(pos);
  LTNode* posprev = pos->prev;
  LTNode* posnext = pos->next;
  free(pos);
  posprev->next = posnext;
  posnext->prev = posprev;
}

我们可以通过复用LTInsert,实现链表的头删和尾删,使程序更简单。

  • 当pos传递的是phead->next,就可以实现头删
  • 当pos传递的是phead->prev,就可以实现尾插删
//头删
void LTPopFront(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);
  LTErase(phead->next);
}
//尾删
void LTPopBack(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);
  
  LTErase(phead->prev);
}

通过上面链表的实现,我们已经感受到了带头双向循环链表的方便和简单,它不需要去考虑链表是否有元素,还可以找到前一个元素,在我们使用中提供很大的便利。

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。

相关文章
|
15天前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
31 10
【数据结构】链表从实现到应用,保姆级攻略
|
1月前
|
存储 C语言
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
|
2月前
【数据结构OJ题】环形链表
力扣题目——环形链表
31 3
【数据结构OJ题】环形链表
|
1月前
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
31 4
|
2月前
【数据结构OJ题】复制带随机指针的链表
力扣题目——复制带随机指针的链表
45 1
【数据结构OJ题】复制带随机指针的链表
|
2月前
【数据结构OJ题】环形链表II
力扣题目——环形链表II
20 1
【数据结构OJ题】环形链表II
|
2月前
【数据结构OJ题】相交链表
力扣题目——相交链表
25 1
【数据结构OJ题】相交链表
|
1月前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
|
1月前
|
存储 测试技术
【初阶数据结构篇】双向链表的实现(赋源码)
因为头结点的存在,plist指针始终指向头结点,不会改变。
|
1月前
|
存储 测试技术
【初阶数据结构篇】单链表的实现(附源码)
在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。