单链表详解

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

链表的概念及结构

概念

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


结构

在逻辑上链表应该是这样子的:656a81e74934417b91976a8865df2f1e.png

在现实中是这样子的:

6ed06e2d854a4beb8a8b153eaebc181e.png

注意:

链表在逻辑上时连续的,但是在物理上不一定连续。

现实中的节点一般是从堆上申请出来的 。

从堆上申请的空间,可能是连续的也可能是不连续的。


单链表的实现

结构

单链表结构中有两个数据,一个是存储数据的,还有一个指针指向下一个节点。

typedef int SLTDateType;
typedef struct SListNode
{
  SLTDateType date;
  struct SListNode* next;
}SLTNode;

它的接口有哪些呢?

// 动态申请一个节点
SLTNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SLTNode* plist);
// 单链表尾插
void SListPushBack(SLTNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SLTNode** pplist);
// 单链表头删
void SListPopFront(SLTNode** pplist);
// 单链表查找
// 找到返回这个节点的地址。找不到返回NULL
SLTNode* SListFind(SLTNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SLTNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos);
// 单链表的销毁
void SListDestroy(SLTNode* plist);
//在pos前插入
void SListInsert(SLTNode** pplist,SLTNode* pos, SLTDateType x);
// 删除pos节点
void SListErase(SLTNode** pplist, SLTNode* pos);

申请节点

我们要添加数据,难免要频繁的开辟节点,所以我们分装以个函数专门来开辟节点。

// 动态申请一个节点
SLTNode* BuySListNode(SLTDateType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    //开辟失败结束掉程序
    exit(-1);
  }
  newnode->date = x;
  newnode->next = NULL;
  return newnode;
}

打印链表

打印链表比较简单,只需要遍历一遍链表即可。

void SListPrint(SLTNode* plist)
{
  SLTNode* cur = plist;
  while (cur)
  {
    printf("%d->", cur->date);
    cur = cur->next;
  }
  printf("NULL\n");
}

尾插

尾插时链表可能为空,所以这时就要把将头指向开辟的节点,这是需要改变头,想要改变一级指针,所以就要传一级指针的地址,这时就需要用一个二级指针来接收,如果链表不为空,我们正常尾插就可以,我们需要先找到尾节点,然后就为节点的next指向newnode即可。

// 单链表尾插
void SListPushBack(SLTNode** pplist, SLTDateType x)
{
// 断言pplist一定不为空,为空时程序异常,终止程序
  assert(pplist);
  SLTNode* newnode = BuySListNode(x);
  if (*pplist == NULL)
  {
  //链表为空
    *pplist = newnode;
  }
  else
  {
    SLTNode* cur = *pplist;
    //找尾节点
    while (cur->next)
    {
      cur = cur->next;
    }
    cur->next = newnode;
  }
}

头插

头插同样需要改变头结点,所以还是需要二级指针,头插只需要将newnode的next指向原链表的头,然后将原链表的头指向newnode就可以了。

void SListPushFront(SLTNode** pplist, SLTDateType x)
{
// 断言pplist一定不为空,为空时程序异常,终止程序
  assert(pplist);
  SLTNode* newnode = BuySListNode(x);
  newnode->next = *pplist;
  (*pplist) = newnode;
}

尾删

尾删只剩一个节点时同样的需要改变头指针,这时free掉头结点,将其置NULL即可。正常情况下,我们只需要找到尾节点的前一个,然后释放掉尾节点,然后把前一个的next置NULL即可。

// 单链表的尾删
void SListPopBack(SLTNode** pplist)
{
// 断言pplist一定不为空,为空时程序异常,终止程序
  assert(pplist);
//断言链表为空就不要删了
  assert(*pplist);
  if ((*pplist)->next == NULL)
  {
    free(*pplist);
    *pplist = NULL;
  }
  else
  {
    SLTNode* tail = *pplist;
    // 至少有两个节点所以tail->next一定不为NULL
    while (tail->next->next)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}

头删

头删一定需要改变头结点,所以同样需要二级指针,我们需要保存头结点的next,让然后释放掉头结点,将头结点重新指向保存下来的next即可。

// 单链表头删
void SListPopFront(SLTNode** pplist)
{
// 断言pplist一定不为空,为空时程序异常,终止程序
  assert(pplist);
//断言链表为空就不要删了
  assert(*pplist);
  //*pplist一定不为NULL
  SLTNode* cur = (*pplist)->next;
  free(*pplist);
  *pplist = cur;
}

查找

查找就很简单了,我们只需要遍历一遍链表即可。

// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDateType x)
{
//断言链表为空就不要查了
  assert(plist);
  SLTNode* cur = plist;
  while (cur)
  {
    if (cur->date == x)
    {
      return cur;
    }
    cur = cur->next;
  }
  return NULL;
}

在pos位置之后插入x

只需要将newnode的next指向pos的next,然后将pos的next指向newnode即可。

// 单链表在pos位置之后插入x
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
  assert(pos);
  SLTNode* newnode = BuySListNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}

在pos位置前面插入

如果是一个节点间的话接相当于头插,我们可以复用上面头插的代码,正常情况下我们需要遍历找到pos的前一个位置,将newnode的next指向pos,再把该节点指向newnode即可。

void SListInsert(SLTNode** pplist, SLTNode* pos, SLTDateType x)
{
  assert(pplist);
  if (pos == *pplist)
  {
    SListPushFront(pplist, x);
  }
  else
  {
    SLTNode* cur = *pplist;
    while (cur->next != pos)
    {
      cur = cur->next;
    }
    SLTNode* newnode = BuySListNode(x);
    newnode->next = pos;
    cur->next = newnode;
  }
}

删除pos之后的值

不能删除最后一个节点,其他情况我们可以直接释放掉pos的next,将pos的next指向下一个即可。

// 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos)
{
  assert(pos);
  assert(pos->next);
  SLTNode* cur = pos->next;
  pos->next = cur->next;
  free(cur);
  cur = NULL;
}

删除pos位置的值

我们需要遍历找pos的前一个位置,然后将pos的前一个位置的next指向pos的next,然后释放掉pos即可,但是如果pos是头结点,我们这样处理不了,我们可以单独处理,相当头删。

void SListErase(SLTNode** pplist, SLTNode* pos)
{
  assert(pplist);
  if (pos == *pplist)
  {
    SListPopFront(pplist);
  }
  else
  {
    SLTNode* cur = *pplist;
    while (cur->next != pos)
    {
      cur = cur->next;
    }
    cur->next = pos->next;
    free(pos);
  }
}

销毁链表

销毁只需要遍历释放即可。

// 单链表的销毁
void SListDestroy(SLTNode* plist)
{
  assert(plist);
  SLTNode* cur = plist;
  while (cur)
  {
    SLTNode* pur = cur;
    cur = cur->next;
    free(pur);
  }
}

到这里对于单链表的增删查改已经讲的差不多了,我们的查找可以充当改,找到那个节点,直接修改date即可。


今天的分享就到这里结束了,感谢大家的支持和关注。

目录
打赏
0
0
0
0
4
分享
相关文章
Java 9中引入的模块系统是什么?它有什么作用?
Java 9中引入的模块系统是什么?它有什么作用?
137 2
如何在 Flutter 中使用 MemoryImage【Flutter 专题 23】
如何在 Flutter 中使用MemoryImage, Flutter 有一些 ImageProvider 的实现,其中之一是 MemoryImage,用于从内存中加载原始图像。由于图像数据已经在内存中,这是最快的 ImageProvider 类型。下面是 Flutter 的 ImgaeProvider 从最快到最慢的列表。 MemoryImage AssetImage FileImage NetworkImage
1035 0
解决SQL报错:索引中丢失IN或OUT參数
解决SQL报错:索引中丢失IN或OUT參数
查询表空间状态,创建表空间,让表空间的大小自动扩展,删除表空间
通过select * from DBA_DATA_FILES可以看到现在数据库中的表空间和状态。 其中AUTOEXTENSIBLE为是否自动扩展。 如果需要关闭自动扩展: alter database datafile 'xxx.dbf' autoextend off; 如果需要打开自动扩展 alter database datafile 'xxx.dbf' autoexten
2032 0
【错误记录】无法打开 “xxx“ , 因为 Apple 无法检查其是否包含恶意软件
【错误记录】无法打开 “xxx“ , 因为 Apple 无法检查其是否包含恶意软件
731 0
【错误记录】无法打开 “xxx“ , 因为 Apple 无法检查其是否包含恶意软件
kettle开发-超好用自定义数据处理组件
kettle开发-超好用自定义数据处理组件
253 0
技术心得:墨卡托投影、地理坐标系、地面分辨率、地图比例尺
技术心得:墨卡托投影、地理坐标系、地面分辨率、地图比例尺
263 0
Vulhub靶场搭建
安装Docker 在安装、使用Docker的过程中出现错误比较多,所以这一节来说明一下如何正确安装最新版本的Docker,(国内机器)并且配置加速器。 一键安装Docker 这是推荐方式。在未安装过Docker的机器上,root权限执行如下命令即可一键安装最新版Docker: curl -s https://get.docker.com/ | sh 如果你已经安装过老版本Docker(且不是用这个一键安装脚本安装的),请先卸载Docker(例如sudo apt purge --autoremove docker.io)。 如果你不想使用这种方式安装Docker,也可以使用系统自带的包管理工具来
676 1
加载中,加载中......使用SwiftUI设计2种Loading动画
加载中,加载中......使用SwiftUI设计2种Loading动画
496 0

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等