数据结构基础:一篇文章教你单链表(头插,尾插,查找,头删等的解析和代码)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 数据结构基础:一篇文章教你单链表(头插,尾插,查找,头删等的解析和代码)

和我一起学编程呀,大家一起努力!

这篇文章耗时比较久,所以大家多多支持啦


链表的结构及结构

概念:链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的。

理解:可以把链表理解为火车,每一节火车车厢都有下一节车厢的钥匙

如下图

顺序表不同的是:链表里面的‘车厢’都是独立申请下来的空间,我们称为节点

如上图就可以理解为一个节点

当前节点主要有两个部分组成:要保存的数据和保存下一个节点的地址(指针变量)

为什么需要指针变量保存下一个节点的位置?

因为链表里面的每一个节点都是独立申请的,需要保存下一个节点的位置才能找到下一个节点

假设保存的节点为整型,则代码如图:

struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

给定的链表结构中,如何实现链表的从头到尾的打印

void SLTPrint(SLTNode*phead)
{
 SLTNode*pcur=phead;
 while(pcur)
  {
       printf("%d ",pcur->date);
       pcur=pcur->next;
  } 
  printf("\n");
}

解析

1.pcur指针变量保存第一个节点的地址

2.对pcur解引用拿到next指针变量的地址一个节点的地址

3.赋值给pcur,此时的pcur保存的地址就指向了下一个节点

补充:

1、链式机构在逻辑上是连续的,在物理结构上不一定连续

2、节点一般是从堆上申请的

3、从堆上申请的空间,是按照一定策略分配的,每一次申请的空间可能连续

单链表的存储结构

先写出结构体改名比较简单的名字为SLTNode

typedef int SLTDataType;//便于后期修改
typedef struct SListNode
{
SLTDataType data; //节点数据
struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;

单链表的头插

1、断言,pphead不能为空

2、给新节点申请空间

3、让新节点的next指向原本的头

4、新节点设为新的头

void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
  assert(pphead);
  SLTNode* newnode = SLTBuyNode(x);
  newnode->next = *pphead;
  *pphead = newnode;
}

单链表的尾插

1.分为链表为空和不为空

2.链表为空:头节点直接为新的节点,即newnode

3.链表不为空,找到尾节点,尾节点的next指向新节点newnode

注意:这里使用了二级指针,因为你需要传的是地址

//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
    assert(pphead);
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  newnode->date = x;
  newnode->next = NULL;
  //链表为空
  if (*pphead == NULL)
  {
    *pphead = newnode;
    return;
  }
  //链表不为空
  SLTNode* ptail = *pphead;
  while (ptail->next)
  {
    ptail = ptail->next;
  }
  ptail->next = newnode;
}
void SListTest02()//尾插
{
  SLTNode* plist = NULL;
  SLTPushBack(&plist, 1);
  SLTPrint(plist);
}

单链表的头部删除

1、pphead,*pphead不能为空

2、保存第二个节点

3、释放旧的头也就是*pphead

4、把保存的第二个节点设为头结点

void SLTPopFront(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);
  //第二个节点成为新的头结点,释放旧的头结点
  SLTNode* next = (*pphead)->next;
  free(*pphead);
  *pphead = next;
}

单链表的尾部删除

1、分为两种情况,一个是链表只有一个节点的情况,另一个是不止有一个节点的情况,当然这都是在pphead *phead不为空的情况

2、只有一个节点的情况就是释放(free)

3、不只有一个节点:就是找到尾节点前面一个节点,让这个节点(prev)指向NULL找到尾节点,使用while循坏,把头结点保存在ptail中,当ptail->next为NULL就终止循坏,找到了prev    prev->next指向空指针,然后释放掉之前的尾节点

void SLTPopBack(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);
  //一个节点
  if ((*pphead)->next == NULL)
  {
    free(*pphead);
    *pphead = NULL;
    return;
  }
  SLTNode* ptail = *pphead;
  SLTNode* prev = NULL;
  while (ptail->next)
  {
    prev = ptail;
    ptail = ptail->next;
  }
  prev->next = NULL;
  //销毁
  free(ptail);
  ptail = NULL;
}

单链表的查找

1、断言pphead

2、遍历链表,采用循坏就可以,找到了就返回x

SLTNode* SLTFind(SLTNode** pphead, SLTDateType x)
{
  assert(pphead);
  //遍历链表
  SLTNode* pcur = *pphead;
  while (pcur)
  {
    if (pcur->date == x)
      return x;
    pcur = pcur->next;
  }
  return NULL;
}

在指定位置之前插入数据

1、pphead *pphead pos不能为空

2、分为两种情况,pos是头结点,采用头插

3、pos不是头结点,采用while循坏,找到pos前面的一个节点(prev),prev->next指向新节点,新节点->next指向pos

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
  assert(pphead);
  assert(pos);
  assert(*pphead);
 
  //Pos是头结点
  if (pos == *pphead)
  {
    SLTPushFront(pphead, x);
    return;
  }
  //不是头结点情况
  SLTNode* newnode = SLTBuyNode(x);
  SLTNode* prev = *pphead;
  
  while (prev->next != pos)
  {
    prev = prev->next;
  }
  prev->next = newnode;
  newnode->next = pos;
}

在指定位置之后插入数据

1、直接申请新节点空间

2、新节点的->next指向pos的后面一个节点

3、pos—>next指向新节点

void SLTInsertAfter(SLTNode* pos, SLTDateType x)
{
  assert(pos);
  SLTNode* newnode = SLTBuyNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}

删除pos节点

1、pos是头结点,采用头删

2、pos不是头节点,找到pos节点的前面一个节点(prev),让prev->next指向pos->next,然后释放pos节点

void SLTErase(SLTNode** pphead, SLTNode*pos)
{
  assert(pphead);
  assert(*pphead);
  assert(pos);
 
  //posg刚好是头结点
  if (*pphead == pos)
  {
    SLTPopFront(pphead);
    return;
  }
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    prev = prev->next;
  }
  prev->next = pos->next;
  free(pos);
  pos = NULL;
}

销毁链表

采用循坏,依次把每一个节点释放掉

void SListDesTroy(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);;
 
  SLTNode* pcur = *pphead;
  while (pcur)
  {
    SLTNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  *pphead = NULL;
}

希望大家多多支持!谢谢大家可以阅读到这里^  ^


相关文章
|
26天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
52 4
|
19天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
42 5
|
17天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
46 1
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
73 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
26天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
42 0
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
49 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
161 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
30 1

推荐镜像

更多