数据结构之单链表

简介: 数据结构之单链表

对于顺序表我们发现,在此基础上仍存在了些许不足:

1.中间或头部的插入时间复杂度为O(n),有点浪费时间。

2.增容需要申请空间,拷贝数据,释放空间,会有不小的消耗。

3.增容一般是呈2倍增容的,势必会造成空间浪费。

如何解决以上问题,我们又了解到了一种新的数据结构-“链表”

1.什么是链表。

链表顾名思义,由一条链子连接表中各成员。实则链表是由每一块独立的空间组成,每一个空间里存放着数据和一个指针,每一个指针指向下一块空间的地址,依次指向,我们可以想象成为用链条串联起类,故叫链表,链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链次序实现的。

1d4916e37a7340deb8de756ff5c4da2b.png就像火车一样

我们一般将这一块空间称为一个节点,链表的末尾节点的指针是空指针。


2.链表的优点

相较于顺序表

1.因为链表是由一块块空间构成,所以链表不存在容量不够要扩容的问题

2.根据需求申请释放空间

3.较好地解决了在头部或中间插入删除挪动数据的问题

3.单链表的实现

这里还是以存放整形数据为例:

1.单链表的结构体

如何来实现这样的结构呢?

cde1359bca174856a6b9a56584321428.png

typedef int SLdatatype;
typedef struct seqlist
{
  SLdatatype data;
  struct seqlist* next;
}SLnode;

可以看到这里的结构体里存放了本结构体的指针,每一个结构体里都有一个自身的指针,而这个自身的指针就代表下一个结构体的首地址,依次下去,直至到末尾next尾空。

用单链表的话来说,就是每一个节点存放着指向下一个节点地址的指针,故此我们可以创建所需的节点个数存放数据,用指针连接起来。

2.节点的创建

SLnode* SLcreatnode(SLdatatype x)
{
  SLnode* newnode = (SLnode*)malloc(sizeof(SLnode));
  if (newnode == NULL)
  {
    perror("开辟错误\n");
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}

每创建一个节点,就是在堆区申请一块空间用来存放数据,利用malloc开辟该结构体大小的空间,之后返回该节点,也就是这片空间。

3.参数的调用

这里强调下参数,因为我们需要改变链表,定义链表变量的时候是指针,函数的参数应设计为二级指针。

对于一个函数我们在其中定义的变量生命周期只在该作用域中,出了作用域就会被销毁,而有的函数需要去改变实参,只是简单的改变形参,是不会影响实参的,但若为实参的地址,我们可通过解引用形参来改变实参,比如:

int swap(int* x, int* y)
{
  int tmp = *x;
  *x = *y;
  *y = tmp;
}
int main()
{
  int x = 10;
  int y = 20;
  swap(&x, &y);
  printf("%d %d", x, y);
  return 0;
}

因此,这里要想通过形参改变实参,参数设计为二级指针:这里以头插为例

5b67ea0a56a54daa9d0050e50bee6fca.png

4.头插

void SLpushfront(SLnode** p, SLdatatype x)//传参用二级指针
{
  SLnode* newnode = SLcreatnode(x);
  newnode->next = *p;
  *p = newnode;
}

1c8a28a893f249c18fbf20a43c38ded1.png

可以看到头插是先malloc一个节点赋值给newnode,之后在将该newnode 存放到目前的头节点的next中,然后赋值给头节点,即实现头部插入。

5.尾插

void SLpushback(SLnode** p, SLdatatype x)//尾插
{
  SLnode* newnode = SLcreatnode(x);
  //链表为空的话
  if (*p == NULL)
  {
    *p = newnode;
    (*p)->data = x;
  }
  else
  {
//找尾巴(前提:链表不为空)
  SLnode* tail = *p;
  while (tail ->next!= NULL)
  {
    tail = tail -> next;
  }
  tail->next = newnode;
  (*p)->data = x;
  }
}

尾插需要我们找到最后一个节点,之后先改变末尾的next,(因为保证是连接的)然后再插入。

7.头删

void SLpopfront(SLnode** p)//头删
{
  assert(*p);
  if ((*p) ->next== NULL)
  {
    free(p);
    *p = NULL;
  }
  else
  {
    SLnode* top = *p;
  *p = (*p) -> next;//指向下一个
  free(top);
  top = NULL;
  }
}

头删需要分情况

8.尾删

void SLpopback1(SLnode** p)//尾删
{
  SLnode* pre = NULL;
  SLnode* tail = *p;
  while (tail->next != NULL)
  {
  pre = tail;//找到尾节点前一个
  tail = tail->next;
  }
  free(tail);//释放最后的空间
  pre->next = NULL;//同时前一个的next为空
}

或者

void SLpopback2(SLnode** p)
{
  SLnode* tail = *p;
  while(tail->next->next)//找倒数第二个节点
  {
    tail = tail->next;
  }
  free(tail->next);//释放
  tail->next = NULL;//倒数第二个next置空
}

主要找到最后一个节点或者最后一个的前一个节点(这里的两种方法),在释放掉最后一个同时,前一个节点next为NULL。可以看到第二种直接释放掉前一个的next空间,即指向最后一个结点的空间,在置空。

0228d3fd1bab42a18100c831f0c90989.png

8.在pos节点前插入x

void SLinsert(SLnode** p, SLnode* pos, SLdatatype x)//插在pos位置前
{
  SLnode* newnode = SLcreatnode(x);
  if (pos == *p)
  {
    SLpushfront(p, x);
  }
  SLnode* prev=*p;
  while (prev->next != pos)
  {
    prev = prev->next;
  }
  prev->next = newnode;
  newnode->next = pos;
}

450d8de9c2e34e54999d89a19579dddd.png

在这里需要注意在遍历找到pos位置前的一个节点时,我们都是prev->next==pos这样去寻找,但忽略了当头节点就是pos位置时的节点,故在开始我们还需要判断是否头节点等于pos节点,如何果实就直接调用头插

9.在pos节点前插入x

//pos节点后插
void SLinsertafter(SLnode** p, SLnode* pos, SLdatatype x)
{
  SLnode* newnode = SLcreatnode(x);
  SLnode* prev = pos->next;
  pos->next = newnode;
  newnode->next = prev;
}

10.删除pos位置节点

void SLerase(SLnode** p, SLnode* pos)//删除pos位置的节点
{
  SLnode* prev = *p;
  if (*p == pos)
  {
    SLpopfront(p);
  }
  else
  {
      while (prev->next != pos)
     {
    prev = prev->next;
     }
  prev->next = pos->next;
  free(pos);
  }
}


06ba679e27a54feeb6da1493f039e568.png


相关文章
|
12天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
30 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
62 4
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
39 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
2月前
|
存储
[数据结构] -- 单链表
[数据结构] -- 单链表
26 1
|
2月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
26 7
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
26 3
|
2月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
20 0
数据结构与算法学习五:双链表的增、删、改、查
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
43 0