【数据结构】线性表之单链表(讲解实现——带动图理解)(1)

简介: 单链表单链表的优点1.头部和中间插入或删除数据效率高,无需挪动。2.按照需求申请释放空间,无需担心空间不够用。单链表的缺点1.不可以进行下标随机访问。2.复杂度是O(n)3.反向遍历困难单链表是线性表的一种,单链表是链式存储的线性表,不同于单链表,链表在内存空间中不连续,而是由结构体内的next指针下一条数据进行链接🧐

单链表

  • 单链表的优点

1.头部和中间插入或删除数据效率高,无需挪动。

2.按照需求申请释放空间,无需担心空间不够用。

  • 单链表的缺点

1.不可以进行下标随机访问。

2.复杂度是O(n)

3.反向遍历困难

单链表是线性表的一种,单链表是链式存储的线性表,不同于单链表,链表在内存空间中不连续,而是由结构体内的next指针下一条数据进行链接🧐

PS:需要源码直接通过目录跳转到最后

单链表主体结构

默认大小与扩容大小还有类型都可以是任意大小或类型

typedef int LLDataType;       //数据类型
typedef struct LinkedList
{
  LLDataType data;          //用于存放数据
  struct LinkedList* next;      //指向下一个数据
}LinkedList;

8af3628152e54b6885900182dff9a3b0.png

单链表操作函数介绍

void printLL(LinkedList* Phead); //打印单链表

void LLPushBack(LinkedList** PPhead, LLDataType x); //尾插

void LLPushFront(LinkedList** PPhead, LLDataType x); //头插

void LLPopBack(LinkedList** PPhead); //尾删

void LLPopFront(LinkedList** PPhead); //尾插

LinkedList* LListFind(LinkedList* phead, LLDataType x); //查找

void SListInsertAfter(LinkedList* pos, LLDataType x); //在指定位置后插入

void SListEraseAfter(LinkedList* pos); //删除指定位置后的一个表

void SListDestroy(LinkedList** Phead); //销毁单链表

void SListInsertFront(LinkedList** Phead, LinkedList* pos, LLDataType x); //在指定位置前插入

void SListErase(LinkedList** PPhead, LinkedList* pos); //删除指定位置数据

为了代码方便阅读,讲单链表操作函数全部放在LinkedList.c文件中,将头文件放在LinkedList.h,测试文件test.c 😮

单链表操作函数实现

为了方便调试,建议每写完1-2个函数就进行测试,初始化之后,首先实现print函数可以方便我们进行调试。

单链表的初始化:

LinkedList *Phead = NULL; //未进行插入之前,头结点指针指向NULL

必须先进性初始化,讲链表指向NULL避免野指针问题。

打印函数

写完插入立马写打印,方便进行调试

void printLL(LinkedList* Phead)
{
  while (Phead!=NULL)
  {
    printf("%d->", Phead->data);
    Phead = Phead->next;
  }
  printf("NULL");
}

单链表插入函数:

头插

头插将新结点插入到头结点

1.如果头结点不为空,头结点改为新插入的结点,原本的第一个位置的结点需要连接到新结点的next。

2.如果头结点为空,那么直接将新结点给到头结点指针phead

  • 既然有插入就一定会有空间不够用的问题,这里用到检查空间是否够用的一个函数
LinkedList* BuyNewNode(LLDataType x)    //申请一个新结点并将next指向空
{
  LinkedList* ret = (LinkedList*)malloc(sizeof(LinkedList));
  if (ret == NULL)
  {
    perror("Malloc:");
    return NULL;
  }
  ret->data = x;
  ret->next = NULL;
  return ret;
}
void LLPushFront(LinkedList** PPhead, LLDataType x)
 // 使用二级指针是因为我们要改变头结点,所有必须要使用头结点的指针来改变。
{
  assert(PPhead);     
  LinkedList* NewNode = BuyNewNode(x);
  if (*PPhead == NULL)
  {
    *PPhead = NewNode;
  }
  else
  {
    NewNode->next = *PPhead;
    *PPhead = NewNode;
  }
}

头结点为空

  • 如果头结点为空,那么直接将新结点给到头结点指针phead


45da6f11c7554587918f08c6bf39aa05.gif

头结点不为空

  • 如果头结点不为空,头结点改为新插入的结点,原本的第一个位置的结点需要连接到新结点的next。


7945a9120e8541ed80b6166a7783c0d5.gif

尾插

从最后一个结点后面插入新结点

  • 尾插也会用到申请新结点的函数,这里不重复了
void LLPushBack(LinkedList** PPhead, LLDataType x)
// 使用二级指针是因为我们要改变头结点,所有必须要使用头结点的指针来改变。
{
  assert(PPhead);
  LinkedList* NewNode = BuyNewNode(x);  //也用到了上面的BugNode不重复了
  if (*PPhead == NULL)
  {
    *PPhead = NewNode;
  }
  else
  {
    LinkedList* tail = *PPhead;
    while (tail->next != NULL)
    {
      tail = tail->next;
    }
    tail->next = NewNode;
  }
}

头结点不为空

  • 如果头结点不为空,先找到最后一个结点(指向NULL的就是最后一个结点),然后将新结点插入到最后一个结点的后面

ce9ddee5ad714ab58d6a3665e0a6036e.gif

头结点为空

  • 如果头结点为空,那么直接将新结点给到头结点指针phead(与上面头插头结点为空相同)

fbfcf144cec14f4c9f47cf13999b15eb.gif

指定结点后插入和查找函数

相对上面两个简单,下面插入函数都需搭配查找结点函数使用,查找函数就是遍历,找到返回地址,比较简单,就不多描述了。

LinkedList* LListFind(LinkedList* phead, LLDataType x)
{
  assert(phead);
  LinkedList *ret = phead;
  while (ret->data != x)
  {
    if (ret == NULL)
    {
      return NULL;
    }
    ret = ret->next;
  }
  return ret;
}
void SListInsertAfter(LinkedList* pos, LLDataType x)
{
  assert(pos);
  LinkedList *newnode = BuyNewNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}


image.gif

单链表结点之前插入数据

void SListInsertFront(LinkedList** PPhead, LinkedList* pos, LLDataType x)
{
  assert(pos);
  assert(PPhead);
  if (*PPhead == NULL||*PPhead==pos)
  {
    LLPushFront(PPhead, x);
  }
  else
  {
    LinkedList* cur = *PPhead;
    while (cur->next != pos)
    {
      cur = cur->next;
    }
    LinkedList* newnode = BuyNewNode(x);
    newnode->next = cur->next;
    cur->next = newnode;
  }
}
  • 头结点为空或插入结点就是头结点指向的结点,直接复用头插函数即可

ebce4ba8c12a40c880e808bcc6428bd0.gif




























相关文章
|
2月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
67 4
|
2天前
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
25 7
|
2天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
20 5
|
2天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
20 5
|
16天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
78 5
|
2月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
126 4
|
2月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
57 0
|
2月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
86 0
|
7月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表