【数据结构】详解链表(一)——单链表(动图讲解)

简介: 【数据结构】详解链表(一)——单链表(动图讲解)

作者:一个喜欢猫咪的的程序员

专栏:《数据结构》

喜欢的话:世间因为少年的挺身而出,而更加瑰丽。                                  ——《人民日报》

目录

前言:

单链表的结构和形成:

逻辑结构:

物理结构(从计算机角度):

BuySLTNode(创建结构体):

CreateSList部分(n个结构体形成链表):

PrintSList(打印单链表):

单链表的功能实现(增删查改):

SLTPushBack尾插部分:

当plist不为NULL时:

当plist为NULL时:

SLTPopBack(尾删部分):

SLTPushFront(头插部分):

SLTPopFront(头删部分):

SLTFide(查找部分):

SLTInsertAfter(在pos位置之后插入):

SLTInsertFront(在pos位置之前插入):

SLTEraseAfter(删除pos之后的位置):

SLTErase(删除pos位置):

SLTDestory(释放空间):

Slist.h头文件引用和结构体定义:

Slist.c各种功能函数实现:

Test.c测试文件:


前言:


单链表是一种链式存取的 数据结构 ,用一组地址任意的 存储单元 存放线性表中的数据元素。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素 存储 位置),元素就是存储数据的存储单元,指针就是连接每个结点的 地址 数据。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。 以“结点的序列”表示线性表称作 线性链表 (单链表),单链表是链式存取的结构。 链接方式存储的线性表简称为链表(Linked List)。 ② 链表中结点的逻辑次序和物理次序不一定相同。


单链表的结构和形成:


将一个结构体包含两个部分,一个是数据,一个是与本身结构体同类型的结构体指针

typedef int SLDataType;
ct SListNode {//单链表
  SLDataType data;//数据
  struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
}SLTNode;

这样就可以在结构体指针找到下一个结构体的位置,第一个指针找到了第二个结构体,第二个结构体指针找到第三个结构体,以此类推....这样就形成了一个链表。


逻辑结构

但计算机实际上并没有指向的箭头啊,我们可以通过指针指向下一个结构体的地址来实现!


物理结构(从计算机角度):


BuySLTNode(创建结构体):


我们是动态开辟malloc结构体,malloc函数可能会异地扩、本地扩或者NULL,因此我们需要判断malloc返回值是否为空!以及我们需要把地址传回去,防止异地扩后,地址不同!


对于malloc函数不熟悉的同学可以看看我的另外一篇博客:对动态内存开辟有详细解释

http://t.csdn.cn/IkDCF

http://t.csdn.cn/IkDCF

言归正传。我们来看一下这个函数如何实现:

SLTNode* BuySLTNode(SLDataType x)
{//创建结构体
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}

CreateSList部分(n个结构体形成链表):

我们这种情况是通过写4个语句来将这4个结构体穿起来,那如果我们要n个结构体形成链表呢?

我们结合一个图,来想想怎么把图中代码的能力实现,并实现n个呢? n个结构体相连,所以肯定是for循环,利用BuyTNode创建动态结构体,我们再将下一个结构体的地址传给结构体的next指针以此类推.....

SLTNode* CreateSList(int n)
{//将n个结构体形成链表
  SLTNode* phead = NULL;
  SLTNode* plist = NULL;
  for (int i = 0; i < n; i++)
  {
    SLTNode* newnode = BuySLTNode(i);
    if (plist == NULL)
    {
      phead = plist = newnode;
    }
    else
    {
      plist->next = newnode;
      plist = newnode;
    }
  }
  return phead;
}
v

因为使用BuySLTNode创建结构体时,next都为NULL。

plist.next=newnode来形成链表,我们这边有一个矛盾,当第一个结构体时,并没有第二个来赋值,我们可以这样:plist=newnode,这样的时候当newnode为第二个结构体的地址的时候就不为NULL,就会将第二个结构体赋值给第一个结构体的next指针将之前的覆盖。


PrintSList(打印单链表):

我们利用一个while循环遍历一遍打印就好。

void* PrintSList(SLTNode* phead)
{//打印链表
SLTNode* cur = phead;
while (cur!= NULL)
//如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
{
printf("[%d|%p]->", cur->data,cur->next);
cur = cur->next;//这里如果想要直接cur->next=...的话有点困难
}
printf("NULL");
}

如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值

单链表的功能实现(增删查改):

SLTPushBack尾插部分:

当plist不为NULL时:

我们尾插要先找尾部,即NULL的地方。然后将newnode插到null后面就好了。

我们来看下面这个尾插是否有错?

可能很多人觉得这样没有问题,但实际上是错的!!!


  • tail为NULL和tail->next为NULL对于循环条件来说是不一样的。
  • tail为NULL代表没有这个结构体,就下图4个结构体为例:当tail指向第4个结构体时,循环不会结束,tail下一个会指向NULL。当循环停止的时候,tail已经指向了NULL,而第四个结构体的next还是指向空,即使我们插进去一个结构体,也会在第四个结构体的时候next到NULL,相当于没有插入!

  • tail->next为NULL代表没有下一个结构体了,这样tail->next=newnode就会将之前的尾部结构体和新加入的结构体连接起来形成链表!

当循环条件为tail.next!=NULL时:

void SLTPushBack(SLTNode* phead, SLDataType x)
{//尾插
//assert(phead);//这里不需要断言,空链表也可以尾插
SLTNode* newnode = BuySLTNode(x);
SLTNode* tail = phead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}

当plist为NULL时:

这个是我在plist为NULL时运行结果的截图:

这里错误是由指针传参导致的!

你一定很疑惑把我明明把指针传过去,传地址不是可以改变原来的值吗?没有错啊.

我们先来复习一下:

  • 你的老师一定跟你说函数在传值调用时,形参不可以改变实参
  • 传址调用才可以改变实参!

这是没有错的,但是它是相对而言的。


  • 指针本质是一个地址,我们所说的指针的值是一个地址,而不是这个地址指向的值。
  • NULL为空指针,NULL=0x0000000000(这是一个地址)。

我们要改变NULL这个指针的时候,注意:我们这里说要改变这个指针是指我们要改变这个地址(0x00000000),而不是这个地址的值。


那我们这里的 SLTNode* phead是不是相当于上图int a。

d5f9a62ff0a2404bba23fd43a9beef7f.gif

因此我们要改变NULL这个指针,我们需要传SLTNode**pphead进去,那我们*pphead就可以改变NULL的值了!

void SLTPushBack(SLTNode** pphead, SLDataType x)
{//尾插
//assert(phead);//这里不需要断言,空链表也可以尾插
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else {
SLTNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}


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