最最容易实现的链表结构——双向链表(数据结构C语言实现4)

简介: 最最容易实现的链表结构——双向链表(数据结构C语言实现4)

双链表结构

单链表

之前我们已经知道单向链表的结构:

逻辑结构


image.png

//类型创建
  typedef int SLDataType;
  typedef struct SListNode
  {
      SLDataType data;   //存值
      struct SListNode* next; //存下一节点的指针
  }SLNode;

结构体存放了一个date数据和一个next结构体指针指向下一个节点!

我们再来看一下物理结构

image.png

这便是单链表,结构简单,但我们实现起来却比较复杂的一种链表结构,我们今天来看一下双向链表!


双向链表

所谓双向链表顾名思义就是,节点方向是双向的,不像单链那样,只能从头节点出发到尾结点!


typedef int LDataType;
typedef struct ListNode
{
 struct ListNode*prev;
 LDataType data;
 struct ListNode*next;
}LisNode;

image.png

从上面的逻辑结构我们可以看出这个双向循环链表是带哨兵位,就是带头,并且第一个节点与最后一个节点相连说明是循环的!所以上面的结构是带头双向循环链表我们今天要实现的链表也是这种最特殊的链表!

链表的分类:

image.png

image.png



image.png

双向链表的实现

image.png

我们先来创建一个双向链表的结构体节点


//节点类型创建
typedef int LDataType;
typedef struct ListNode
{
  struct ListNode* prev;
  LDataType data;
  struct ListNode* next;
}ListNode;

接口实现

创建新的节点


//创建新的节点
ListNode* ListBuyNewNode(LDataType x)
{
  ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  if (newnode)
  {
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
  }
  else
  {
  printf("malloc fail!\n");
  return NULL;
  }
}

链表初始化返回头节点


//初始化链表返回头结点
ListNode* ListInit()
{
  ListNode* head=ListBuyNewNode(0);
  head->next = head;
  head->prev = head;
  return head;
}

链表不用了销毁

//回收
void ListDestroy(ListNode* head)
{
  head->next=head->prev = NULL;
}

打印


//打印
void ListPrint(ListNode* head)
{
  assert(head);
  ListNode* cur = head->next;
  while (cur!=head)
  {
  printf("%d ", cur->data);
  cur = cur->next;
  }
  printf("\n");
}

头插

和单向不带头链表不一样,因为链表带了头节点就只需要传一级指针就可以进行头插!因为头节点是不会改变的


//头插
void ListFrontPush(ListNode* head, LDataType x)
{
  ListNode* newnode = ListBuyNewNode(x);
  newnode->next = head->next;
  newnode->prev = head;
  head->next->prev= newnode;
  head->next = newnode;
}

image.png

**注:**如果我们直接像上面一样不创建其他变量,直接插入节点,我们需要先连接new的指针,再改变head和head->next的指针,先连后改如果我们现将head->next改变指向了new我们就无法找到head->next节点了!其他接口也是如此!

头插接口测试

image.png


头删


//头删
void ListFrontPop(ListNode* head)
{
  ListNode* ret = head->next;
  head->next = ret->next;
  ret->next->prev = head;
  free(ret);
  ret = NULL;
}

头删接口测试

image.png


ListNode* head = ListInit();
  ListFrontPush(head, 1);
  ListFrontPush(head, 2);
  ListFrontPush(head, 3);
  ListFrontPush(head, 4);
  ListPrint(head);
  ListFrontPop(head);
  ListFrontPop(head);
  ListPrint(head);


image.png


//尾插
void ListBackPush(ListNode* head, LDataType x)
{
  ListNode* new = ListBuyNewNode(x);
  new->next = head;
  new->prev = head->prev;
  head->prev->next= new;
  head->prev = new;
}

尾插接口测试


ListNode* head = ListInit();
  ListFrontPush(head, 1);
  ListFrontPush(head, 2);
  ListFrontPush(head, 3);
  ListFrontPush(head, 4);
  ListPrint(head);
  ListBackPush(head, 1);
  ListBackPush(head, 2);
  ListBackPush(head, 3);
  ListBackPush(head, 4);
  ListBackPush(head, 5);
  ListPrint(head);


image.png


//尾删
void ListBackPop(ListNode* head)
{
  ListNode* tail = head->prev;
  head->prev = tail->prev;
  tail->prev->next = head;
  free(tail);
  tail = NULL;
}

尾删接口测试


ListNode* head = ListInit();
  ListFrontPush(head, 1);
  ListFrontPush(head, 2);
  ListFrontPush(head, 3);
  ListFrontPush(head, 4);
  ListPrint(head);
  ListBackPush(head, 1);
  ListBackPush(head, 2);
  ListBackPush(head, 3);
  ListBackPush(head, 4);
  ListBackPush(head, 5);
  ListPrint(head);
  ListBackPop(head);
  ListBackPop(head);
  ListBackPop(head);
  ListPrint(head);

image.png

查找


//查找x节点并返回
ListNode* ListFind(ListNode* head, LDataType x)
{
  ListNode* ret = head->next;
  while (ret!= head)
  {
  if (ret->data == x)
  {
    return ret;
  }
  ret = ret->next;
  }
  return NULL;
}

pos节点修改


//在pos前插入
void ListInsert(ListNode* head, ListNode* pos,LDataType x)
{
  ListNode* new = ListBuyNewNode(x);
  new->next = pos;
  new->prev = pos->prev;
  pos->prev->next = new;
  pos->prev = new;
}

pos节点删除


//pos节点删除
void ListErase(ListNode* head, ListNode* pos)
{
  pos->prev->next = pos->next;
  pos->next->prev = pos->prev;
  free(pos);
  pos = NULL;
}

接口测试


void test1()
{
  ListNode* head = ListInit();
  ListFrontPush(head, 1);
  ListFrontPush(head, 2);
  ListFrontPush(head, 3);
  ListFrontPush(head, 4);
  ListPrint(head);
  ListNode* pos = ListFind(head, 3);
  if (pos)
  {
  ListInsert(head, pos, 33);
  ListPrint(head);
  ListErase(head, pos);
  ListPrint(head);
  }
}

image.png


源码

List.h头文件


#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//节点类型创建
typedef int LDataType;
typedef struct ListNode
{
  struct ListNode* prev;
  LDataType data;
  struct ListNode* next;
}ListNode;
//创建新的节点
ListNode*ListBuyNewNode(LDataType x);
//初始化链表返回头结点
ListNode* ListInit();
//回收
void ListDestroy(ListNode*head);
//打印
void ListPrint(ListNode* head);
//头插
void ListFrontPush(ListNode* head,LDataType x);
//头删
void ListFrontPop(ListNode* head);
//尾插
void ListBackPush(ListNode* head,LDataType x);
//尾删
void ListBackPop(ListNode* head);
//查找
ListNode* ListFind(ListNode* head, LDataType x);
//pos节点删除
void ListErase(ListNode* head, ListNode* pos);
//在pos前插入
void ListInsert(ListNode* head, ListNode* pos , LDataType x);

List.c所有接口源码


//创建新的节点
ListNode* ListBuyNewNode(LDataType x)
{
  ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  if (newnode)
  {
  newnode->data = x;
  newnode->next = NULL;
  newnode->prev = NULL;
  return newnode;
  }
  else
  {
  printf("malloc fail!");
  return NULL;
  }
}
//初始化链表返回头结点
ListNode* ListInit()
{
  ListNode* head=ListBuyNewNode(0);
  head->next = head;
  head->prev = head;
  return head;
}
//回收
void ListDestroy(ListNode* head)
{
  head->next=head->prev = NULL;
}
//打印
void ListPrint(ListNode* head)
{
  assert(head);
  ListNode* cur = head->next;
  while (cur!=head)
  {
  printf("%d ", cur->data);
  cur = cur->next;
  }
  printf("\n");
}
//头插
void ListFrontPush(ListNode* head, LDataType x)
{
  ListNode* newnode = ListBuyNewNode(x);
  newnode->next = head->next;
  newnode->prev = head;
  head->next->prev= newnode;
  head->next = newnode;
}
//头删
void ListFrontPop(ListNode* head)
{
  ListNode* ret = head->next;
  head->next = ret->next;
  ret->next->prev = head;
  free(ret);
  ret = NULL;
}
//尾插
void ListBackPush(ListNode* head, LDataType x)
{
  ListNode* new = ListBuyNewNode(x);
  new->next = head;
  new->prev = head->prev;
  head->prev->next= new;
  head->prev = new;
}
//尾删
void ListBackPop(ListNode* head)
{
  ListNode* tail = head->prev;
  head->prev = tail->prev;
  tail->prev->next = head;
  free(tail);
  tail = NULL;
}
//查找
ListNode* ListFind(ListNode* head, LDataType x)
{
  ListNode* ret = head->next;
  while (ret!= head)
  {
  if (ret->data == x)
  {
    return ret;
  }
  ret = ret->next;
  }
  return NULL;
}
//pos节点删除
void ListErase(ListNode* head, ListNode* pos)
{
  pos->prev->next = pos->next;
  pos->next->prev = pos->prev;
  free(pos);
  pos = NULL;
}
//在pos前插入
void ListInsert(ListNode* head, ListNode* pos,LDataType x)
{
  ListNode* new = ListBuyNewNode(x);
  new->next = pos;
  new->prev = pos->prev;
  pos->prev->next = new;
  pos->prev = new;
}

目录
相关文章
|
1月前
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
71 10
|
1月前
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
40 10
|
1月前
|
存储 编译器 C语言
【C语言程序设计——选择结构程序设计】求一元二次方程的根(头歌实践教学平台习题)【合集】
本任务要求根据求根公式计算并输出一元二次方程的两个实根,精确到小数点后两位。若方程无实根,则输出提示信息。主要内容包括: - **任务描述**:使用求根公式计算一元二次方程的实根。 - **相关知识**:掌握 `sqrt()` 函数的基本使用方法,判断方程是否有实根。 - **编程要求**:根据输入的系数,计算并输出方程的根或提示无实根。 - **测试说明**:提供两组测试数据及预期输出,确保代码正确性。 - **通关代码**:包含完整的 C 语言代码示例,实现上述功能。 通过本任务,你将学会如何处理一元二次方程的求解问题,并熟悉 `sqrt()` 函数的使用。
32 5
|
1月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】
本任务要求从键盘输入三个数,并按从小到大的顺序排序后输出。主要内容包括: - **任务描述**:实现三个数的排序并输出。 - **编程要求**:根据提示在编辑器中补充代码。 - **相关知识**: - 选择结构(if、if-else、switch) - 主要语句类型(条件语句) - 比较操作与交换操作 - **测试说明**:提供两组测试数据及预期输出。 - **通关代码**:完整代码示例。 - **测试结果**:展示测试通过的结果。 通过本任务,你将掌握基本的选择结构和排序算法的应用。祝你成功!
36 4
|
1月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】求阶跃函数的值(头歌实践教学平台习题)【合集】
本任务要求输入x的值,计算并输出特定阶跃函数的结果。主要内容包括: 1. **选择结构基本概念**:介绍if、if-else、switch语句。 2. **主要语句类型**:详细解释if、if-else、switch语句的使用方法。 3. **跃迁函数中变量的取值范围**:说明如何根据条件判断变量范围。 4. **计算阶跃函数的值**:通过示例展示如何根据给定条件计算函数值。 编程要求:在右侧编辑器Begin-End之间补充代码,实现阶跃函数的计算和输出。测试说明提供了多个输入及其预期输出,确保代码正确性。最后提供通关代码和测试结果,帮助理解整个过程。
33 0
|
1月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】判断一个数是不是5和7的倍数(头歌实践教学平台习题)【合集】
本任务要求输入一个正整数,判断其是否同时是5和7的倍数,若是输出&quot;Yes&quot;,否则输出&quot;No&quot;。内容涵盖选择结构的基本概念、主要语句类型(if、if-else、switch)及条件判断逻辑,帮助理解编程中的分支执行与条件表达式。测试用例包括正数、负数及非倍数情况,确保代码逻辑严谨。通关代码示例如下: ```cpp #include &quot;stdio.h&quot; int main(){ int a; scanf(&quot;%d&quot;, &a); if (a &lt;= 0){ printf(&quo
48 0
|
1月前
|
编译器 C语言 C++
【C语言程序设计——选择结构程序设计】求输入的日期是该年的第几天(头歌实践教学平台习题)【合集】
本任务要求编写程序,根据用户输入的年月日(以空格或回车分隔),计算并输出该天是该年的第几天,需注意判断闰年。主要内容包括: 1. **任务描述**:实现从键盘输入年月日,计算该天是当年的第几天。 2. **相关知识**: - `switch` 结构的基本语法及使用注意事项。 - 判断闰年的条件:能被4整除但不能被100整除,或能被400整除的年份为闰年。 3. **编程要求**:根据提示补充代码,确保程序正确处理输入并输出结果。 4. **测试说 示例代码展示了如何使用 `switch` 语句和闰年判断逻辑来完成任务。通过此练习,掌握 `switch` 语句的应用及闰年判断方法。
32 0
|
3月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
151 16
|
3月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
130 0
|
3月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
166 4

热门文章

最新文章