【数据结构】双链表

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

一. 前言

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


二. 带头双向链表接口实现

1.准备工作

 由于实际开发项目中,项目的实现都是采用模块化的方式实现。所以博主在这也采用了模块化的方式。

#pragma once
typedef int LTDataType;
typedef struct ListNode
{
  struct ListNode* next;//下一个节点指针
  struct ListNode* prev;//上一个节点指针
  LTDataType data;//数据域
}LTNode;

为了后续函数功能实现过程中数据类型书写方便性,我们将struct ListNode重命名为LTNode

同时后续好修改数据域类型,我们将数据域类型int重命名为LTDataType.


2. 创建一个节点

不管是后续插入数据还是初始化,我们都先要创建一个节点来存储数据。

所以我们在这设计了一个相关函数,从而避免大量重复的工作。

代码实现:

LTNode* BuyLTNode(LTDataType x)
{
  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->next = NULL;
  newnode->prev = NULL;
  newnode->data = x;
  return newnode;
}

三. 初始化

初始化时,我们支持需要创建一个节点作为哨兵位,并将两个指针同时指向自己即可。

代码实现:

LTNode* LTInit()
{
  LTNode* phead = BuyLTNode(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}

4. 打印

打印比较简单。但需要注意我们是从哨兵位的下一个节点开始打印

代码实现:

void LTPrint(LTNode* phead)
{
  assert(phead);
  LTNode* cur = phead->next;//哨兵位下一个节点
  printf("phead<=>");
  while (cur != phead)
  {
    printf("%d<==>", cur->data);
    cur = cur->next;
  }
  printf("\n");
}

5. 尾插

带头双链表的尾插比较简单。

我们通过哨兵位向前访问即可得到尾节点。在插入数据即可。

代码实现:

void LTPushBack(LTNode* phead, LTDataType x)
{
  assert(phead);
  LTNode* tail = phead->prev;//尾节点
  LTNode* newnode = BuyLTNode(x);//要插入节点
  //插入
  tail->next = newnode;
  newnode->prev = tail;
  newnode->next = phead;
  phead->prev = newnode;
}

6. 尾删

【代码思路】:尾删首先要判断是否还有节点可删。若有,找到尾节点以及尾节点的前一个结点。在释放尾节点,并将新的尾节点和哨兵位链接起来即可。

代码实现:

void LTPopBack(LTNode* phead)
{
  assert(phead);
  assert(phead != phead->next);//还有节点可删
  LTNode* tail = phead->prev;
  LTNode* tailPrev = tail->prev;
  free(tail);
  tailPrev->next = phead;
  phead->prev = tailPrev;
}

7. 头插

要实现头插,首先要强调是插到哨兵位的后面。

【代码思路】:直接将哨兵位,哨兵位的下一个节点以及新节点链接起来即可。

代码实现:

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

8. 头删

【代码思路】:头删和尾删一样,需要是否还有节点可删。若还有元素可删,需要保存哨兵位后面两个节点firstsecond。再释放掉first后,将哨兵位和second链接起来。

代码实现:

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

9. 计算节点个数

【代码思路】:首先保存哨兵位的下一个节点。然后开始向后遍历访问,每次个数加1,直到某节点的下一个节点指向哨兵位为止。

代码实现:

int LTSize(LTNode* phead)
{
  LTNode* cur = phead->next;
  int count = 0;
  while (cur != phead)
  {
    count++;
    cur = cur->next;
  }
  return count;
}

Tips:

  • 可能有部分学者已经注意到或在一些书籍上看到过以下思路:哨兵位的数据域是随机值,是否可以将它用于记录节点个数。每次进行增删查改等操作时,对其进行加1/减1。不仅更加方便得知节点个数,同时还避免该接口的实现。
  • 在这里博主不在这建议这样做,又或者说大部分情况是不能这样做的。原因在于:我们在本篇博客中,数据域为整型,可以用于记录节点个数大下。但在实际开发过程中,数据域可能存储的是浮点数或结构体什么的,那上述思路将导致结果错误。

10. 查找数据

【代码思路】:查找数据,从哨兵位的下一个节点开始,遍历查找。找到返回数据地址,否则返回空指针。

代码实现:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
  assert(phead);
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    if (cur->data == x)
      return cur;
    cur = cur->next;
  }
  return NULL;
}

11. 在任意位置插入数据

【代码思路】:首先需要强调的是,不管是链表还是顺序表,插入数据默认都是前插,在这里也一样。插入数据、插入位置节点、以及前一个结点链接起来即可。

我们直接将

代码实现:

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

12. 在任意位置删除数据

【代码思路】:将该位置前一个和后一个节点链接起来后,再将该位置节点释放。

代码实现:

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

13. 销毁

由于上述节点都是动态开辟的,使用往后需要销毁,释放内存。

代码实现:

void LTDestory(LTNode* phead)
{
  assert(phead);
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    LTNode* next = cur->next;
    free(cur);
    cur = next;
  }
  free(phead);
}

四. 如何10分钟内完成一个完整双链表

在实际开发过程中,我们一般实现实现任意位置插入删除接口的。然后在头插/删和尾插/删等接口中,调用上述两个接口,从而快速达到目的。

List.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
  struct ListNode* next;
  struct ListNode* prev;
  LTDataType data;
}LTNode;
LTNode* BuyLTNode(LTDataType x);//创建节点
LTNode* LTInit();//初始化
void LTPrint(LTNode* phead);//打印
//在pos之前插入x
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置节点
void LTErase(LTNode* pos);
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
int LTSize(LTNode* phead);//节点大小
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTDestory(LTNode* phead);//销毁

List.c:

#include "List.h"
LTNode* BuyLTNode(LTDataType x)//创建节点
{
  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->next = NULL;
  newnode->prev = NULL;
  newnode->data = x;
  return newnode;
}
LTNode* LTInit()//初始化
{
  LTNode* phead = BuyLTNode(0);
  phead->next = phead;
  phead->prev = phead;
  return phead;
}
void LTPrint(LTNode* phead)//打印
{
  assert(phead);
  LTNode* cur = phead->next;
  printf("phead<=>");
  while (cur != phead)
  {
    printf("%d<==>", cur->data);
    cur = cur->next;
  }
  printf("\n");
}
void LTInsert(LTNode* pos, LTDataType x)//任意位置插入
{
  assert(pos);
  LTNode* posPrev = pos->prev;
  LTNode* newnode = BuyLTNode(x);
  posPrev->next = newnode;
  newnode->prev = posPrev;
  newnode->next = pos;
  pos->prev = newnode;
}
void LTErase(LTNode* pos)//任意位置删除
{
  assert(pos);
  LTNode* posPrev = pos->prev;
  LTNode* posNext = pos->next;
  free(pos);
  posPrev->next = posNext;
  posNext->prev = posPrev;
}
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
  assert(phead);
  LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)//尾删
{
  assert(phead);
  assert(phead != phead->next);
  LTErase(phead->prev);
}
void LTPushFront(LTNode* phead, LTDataType x)//头插
{
  assert(phead);
  LTInsert(phead->next, x);
}
void LTPopFront(LTNode* phead)//头删
{
  assert(phead);
  assert(phead != phead->next);
  LTErase(phead->next);
}
int LTSize(LTNode* phead)//节点大小
{
  LTNode* cur = phead->next;
  int count = 0;
  while (cur != phead)
  {
    count++;
    cur = cur->next;
  }
  return count;
}
LTNode* LTFind(LTNode* phead, LTDataType x)//查找数据
{
  assert(phead);
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    if (cur->data == x)
      return cur;
    cur = cur->next;
  }
  return NULL;
}
void LTDestory(LTNode* phead)//销毁
{
  assert(phead);
  LTNode* cur = phead->next;
  while (cur != phead)
  {
    LTNode* next = cur->next;
    free(cur);
    cur = next;
  }
  free(phead);
}


相关文章
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
226 4
|
10月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
383 30
|
10月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
459 25
|
11月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
530 5
|
12月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
358 5
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
1173 4
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
142 7

热门文章

最新文章