数据结构第三章:单链表的学习

目录

  • 一.单链表的引入
  • 二.单链表头文件展示
  • 三.单链表逐个函数讲解
    • 1.动态申请一个节点
    • 2.单链表打印
    • 3.单链表的尾插
    • 4.单链表的头插
    • 5.单链表的头删
    • 6.单链表的尾删
    • 7.单链表数据的查找
    • 8.单链表任意位置插入
    • 9.单链表任意位置删除
  • 四.单链表总结

一.单链表的引入

因为在顺序表存储的过程中,当空间不够时,会集中进行空间的扩容。在后期使用期间,如果空间需求很小就会造成空间的浪费。但是顺序表也有自己的优点,比如:可以快速查找数据。一下是两种存储结构的对比:

对比维度 顺序表 单链表
内存分配 连续内存空间 离散内存空间(堆区动态申请节点)
访问方式 随机访问,通过下标O(1)直接定位 顺序访问,O(n)遍历指针
插入删除 中间元素O(n),两头元素O(1)。需要移动后续元素 中间插入O(1),需要O(n)查找前驱指针。
空间效率 无额外开销,但是可能存在冗余空间 按需申请节点,动态扩容。
底层依赖 依赖数组的下标映射机制 依赖指针的地址关联机制
适用场景 频繁数据访问,数据量稳定 频繁插入删除,数据量频繁动态改变

二.单链表头文件展示

c 复制代码
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
  SLTDateType data;
  struct SListNode* next;
}SListNode;
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

上述代码的具体解释:首先利用关键字typedef将int更改为SLTDateType,方便后续处理更多数据类型的数据。接下来创建节点结构体,结构体成员变量分别是数据本身和下一个节点的指针。后面的代码全都是相关函数的声明,具体函数实现将在下面一一讲解。

三.单链表逐个函数讲解

1.动态申请一个节点

c 复制代码
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x)
{
  SListNode * newNode=(SLTDateNode *)malloc(sizeof (SListNode));
  if (newNode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newNode->data = x;
  newNode->next = NULL;
  return newNode;
}

上述代码的具体解释:利用函数malloc函数向操作系统申请新的空间,然后将其强制类型转换为结构体指针类型并且赋值给newNode。如果newNode为空指针,则说明申请失败,打印失败信息,返回-1表示非正常返回。申请成功则将结构体成员初始化即可。

2.单链表打印

c 复制代码
void SListPrint(SLTNode *plist)
{
  SLTNode *cur =plist;
  while(cur!= NULL)
  {
    printf("%d",cur->data);
    cur=cur->next;
  }
}

上述代码的具体解释:首先创建一个指针变量指向单链表头节点的位置,然后遍历单链表的所有元素,打印所有节点的数据,

3.单链表的尾插

c 复制代码
void SListPushBack(SLTNode ** pphead,SLDataType x)
{
  SLTNode * newnode=(SLTNode *)malloc(sizeof (SLTNode));
  newnode->data=x;
  newnode->next=NULL;
  if(* pphead ==NULL)
  {
    *pphead=newnode;
  }
  else 
  {
    SLTNode *tail =*pphead;
    while(tail->next!=NULL)
    {
      tail =tail->next;
    }
    tail->next=newnode;
  }
}

上述代码的具体解释:因为尾插会改变头节点的数据,所以函数的形式参数需要用到二级指针。接下来申请空间用于存放尾插的数据,如果单链表的内容为空,则直接将申请好的空间赋值给头指针。如果单链表内容不是空,则首先遍历链表到最后一个节点处,然后将申请来的节点地址赋值给最后一个节点的next指针处。

4.单链表的头插

c 复制代码
void SListPushFront(SLTNode **pphead,SLTDataType x)
{
  SLTNode *newnode=BuySListNode(x);
  newnode->next=*pphead;
  *pphead=newnode;
}

上述代码的具体解释:首先向操作空间申请一个空间用于存放头插的数据,将该数据的next指针指向头节点的地址,更新头节点的位置到新申请的位置处。

5.单链表的头删

c 复制代码
void SListPopFront(SLTNode **pphead)
{
  SLTNode*next=(*pphead)->next;
  free(*pphead);
  *pphead=next;
}

上述代码的具体解释:首先将头节点存储的next指针复制给next。(提前保存,防止释放头节点之后的数据丢失)最后将next指针赋值给头节点就完成了单链表的头删。

6.单链表的尾删

c 复制代码
void SListPopBack(SLTNode ** pphead)
{
  if (*pphead ==NULL)
  {
    return;
  }
  else if((*pphead)->next==NULL)
  {
    free(*pphead);
    *pphead=NULL;
  }
  else 
  {
    SLTNode *prev=NULL;
    SLTNode *tail=*pphead;
    while(tail->next!=NULL)
    {
      prev=tail;
      tail=tail->next;
    }
    free(tail);
    prev->next=NULL;
  }
}

上述代码的具体解释:当单链表为空链表时,直接返回不用继续进行删除数据。当单链表为一个节点时,直接释放掉这个节点的空间就可以完成尾删。当单链表为一个以上的节点时,需要先遍历到倒数第二个节点的位置,然后释放最后一个节点的空间。

所以问题简化为如何遍历到倒数第二个节点的位置。这里需要用到两个指针,首先将两个指针同时指向头部位置,然后只要tail的next指针不为NULL,就将另外一个指针prev指向该tail的位置。直到tail的next指针为NULL时,则prev指向的位置就是倒数第二个节点。随后将倒数第一个节点进行空间的释放,将倒数第二个节点的next指针置为NULL。变成新的最后一个节点。

7.单链表数据的查找

c 复制代码
SLTNode * SListFind(SLTNode *phead,SLTDataType x)
{
  SListNode *cur=phead;
  while(cur!=NULL)
  {
    if(cur->data==x)
    {
      return cur;
    }
    cur =cur->next;
  }
  return NULL;
}

上述代码的具体解释:要找到数据为x的单链表位置,直接用while循环开始遍历,找到则返回当前指针。没有找到则返回NULL。

8.单链表任意位置插入

c 复制代码
void SListInsert(SLTNode**pphead,SLTNode*pos,SLTDaType x)
{
  if(pos ==*pphead)
  {
    SListPushFront(pphead,x);
  }
  else 
  {
    SLTNode *newnode= BuySListNode(x);
    SLTNode *prev= *pphead;
    while(prev->next!=pos)
    {
      prev=prev->next;
    }
    prev->next=newnode;
    newnode->next=pos;
  }
}

上述代码的具体解释:当任意位置为头部时,直接调用头插函数即可。当任意位置不为头时,需要先申请空间用于存储需要插入的数据。接下来遍历单链表到要插入的位置前,进行数据的插入。这里分情况谈论是因为第二种情况在头插时会出现问题。(prev的next指针没有查看是否等于pos该位置)。

9.单链表任意位置删除

c 复制代码
void SListErase(SLTNode **pphead,SLTNode*pos)
{
  if(pos==*pphead)
  {
    SListPopFront(pphead);
  }
  else
  {
    SLTNode*prev=*pphead;
    while(prev->next!=pos)
    {
      prev=prev->next;
    }
    prev->next=pos->next;
    free(pos);
  }
}

上述代码的具体解释:与上述任意位置插入相同,任意位置删除也需要分两种情况谈论。当任意位置为头部时,调用头删函数即可。当不是头删时,则需要用相同的方法遍历到需要删除位置的前一个,然后将删除位置的前后两个节点对接,最后释放需要删除数据的空间即可。

四.单链表总结

单链表相比于顺序表有些许的优点,在本章内容开头详细说明了。但是单链表也有一些缺点待解决。接下来要讲的双向链表就会解决一下单链表的问题。

  1. 反向访问困难,无法直接通过节点获取前驱,需要从单链表头部遍历,时间复杂度O(n)。
  2. 删除指定节点效率低,如果仅知道节点指针。需要先遍历找前驱。
  3. 遍历灵活性差,仅支持从表头到表尾的单向遍历,无法双向切换。
相关推荐
xlq223222 小时前
15.list(上)
数据结构·c++·list
我不会插花弄玉2 小时前
排序【由浅入深-数据结构】
c语言·数据结构
Knox_Lai4 小时前
数据结构与算法学习(0)-常见数据结构和算法
c语言·数据结构·学习·算法
blammmp5 小时前
算法专题二十:贪心算法
数据结构·算法·贪心算法
小白程序员成长日记5 小时前
2025.11.17 力扣每日一题
数据结构·算法·leetcode
时间醉酒7 小时前
数据结构:栈详解-从原理到实现(顺序栈与链式栈)
c语言·数据结构·c++·算法·链表
立志成为大牛的小牛7 小时前
数据结构——四十八、B树(王道408)
数据结构·笔记·b树·学习·考研·算法
大千AI助手7 小时前
二叉树:机器学习中不可或缺的数据结构
数据结构·人工智能·机器学习·二叉树·tree·大千ai助手·非线性数据结构