数据结构之单链表

简介: 数据结构之单链表

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

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


相关文章
|
2月前
【数据结构】单链表(长期维护)(1)
【数据结构】单链表(长期维护)(1)
|
8天前
|
Java
java数据结构,双向链表的实现
文章介绍了双向链表的实现,包括数据结构定义、插入和删除操作的代码实现,以及双向链表的其他操作方法,并提供了完整的Java代码实现。
java数据结构,双向链表的实现
|
8天前
|
存储 Java
java数据结构,线性表链式存储(单链表)的实现
文章讲解了单链表的基本概念和Java实现,包括头指针、尾节点和节点结构。提供了实现代码,包括数据结构、接口定义和具体实现类。通过测试代码演示了单链表的基本操作,如添加、删除、更新和查找元素,并总结了操作的时间复杂度。
java数据结构,线性表链式存储(单链表)的实现
|
1月前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
44 10
【数据结构】链表从实现到应用,保姆级攻略
|
26天前
|
存储 算法 C语言
数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解
本文详细介绍了单链表的理论知识,涵盖单链表的定义、优点与缺点,并通过示例代码讲解了单链表的初始化、插入、删除、查找等核心操作。文中还具体分析了按位序插入、指定节点前后插入、按位序删除及按值查找等算法实现,并提供了尾插法和头插法建立单链表的方法,帮助读者深入理解单链表的基本原理与应用技巧。
|
2月前
|
存储 C语言
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
|
3月前
【数据结构OJ题】复制带随机指针的链表
力扣题目——复制带随机指针的链表
48 1
【数据结构OJ题】复制带随机指针的链表
|
3月前
【数据结构OJ题】环形链表II
力扣题目——环形链表II
23 1
【数据结构OJ题】环形链表II
|
2月前
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
83 4
|
2月前
|
存储
【数据结构】单链表-->详细讲解,后赋源码
【数据结构】单链表-->详细讲解,后赋源码
24 4