当前位置: 首页 > news >正文

【数据结构】单链表的实现

目录

1 链表的概念及结构

2 链表的增删改查实现

2.1 定义节点的结构

2.2 申请一个新节点

2.3 链表打印

2.4 链表的尾插

 2.5 链表的头插

2.6 链表的尾删 

2.7 链表的头删

2.8 链表数据的查找 

2.9 在指定位置之前插入数据

2.10 在指定位置之后插入数据

 2.11 删除指定位置的节点

2.12 删除pos之后的节点

2.13 链表的销毁

3 链表实现完整代码

3.1 SList.h文件 

3.2 SList.c文件

3.3 test.c文件


1 链表的概念及结构

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

与顺序表不同的是,链表里的每个“模块”都是独立申请下来的空间,我们称之为“节点”

节点有两个组成部分:当前节点要保存的数据(1、2、3...)和保存下一个节点的地址(指针变量)。图中指针变量plist保存的是第一个节点的地址,我们称plist此时指向第一个节点,如果我们希望plist指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0。每一个节点都是独立申请的,我们需要通过指针变量来保存下一个节点的位置,才能从当前节点找到下一个节点。

2 链表的增删改查实现

2.1 定义节点的结构

根据结构体来定义节点的结构:

//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型typedef struct SListNode//定义节点
{SLTDataType data;struct SListNode* next;
}SLTNode;

假设当我们想要通过链表来保存整型数据时,实际是先向操作系统申请一块内存,这块内存不仅要保存整型数据,也需要保存下一个节点的地址。

2.2 申请一个新节点

//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}

当我们申请一个新节点时,利用malloc去开辟一个节点大小的空间,然后将数据赋值为x,当前节点储存下一个节点的地址初始化为NULL。

2.3 链表打印

打印链表的所有数据时,定义一个临时变量pcur来储存链表头节点的地址,然后打印当前节点的数据 ,然后走到下一个节点,直到打印完为止,代码如下:

//链表打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

2.4 链表的尾插

链表的尾插就是在当前链表最后一个节点处插入一个新的节点。

尾插函数的参数要进行传址调用,原因在于要实际改变链表的数据时,就需要传地址。想要改变节点,就要创建一个结构体指针变量,例如SLTNode* plist = NULL;调用尾插函数时,就要传递一级指针变量的地址,函数的参数自然就需要二级指针来接收。后续的不同插入和删除操作函数的参数都需要二级指针,代码如下:

//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//创建新节点if (*pphead == NULL){*pphead = newnode;}else{//找尾SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}

 2.5 链表的头插

链表的头插就是在链表第一个节点前插入一个新的节点。

代码如下: 

//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//创建新节点newnode->next = *pphead;*pphead = newnode;
}

2.6 链表的尾删 

链表的尾删就是删除链表的最后一个节点,这里需要两种情况讨论:如果链表只有一个节点,直接释放这个节点的地址,如果有两个或两个以上的节点,释放掉最后一个节点后,倒数第二个节点因为存储着最后一个节点的地址,它存储的地址也要置为NULL。

代码如下:

//链表尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead&&*pphead);if ((*pphead)->next == NULL)//链表如果只有一个节点{free(*pphead);*pphead = NULL;}else{SLTNode* ptail = *pphead;SLTNode* prev = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}
}

2.7 链表的头删

链表的头删是删除链表的第一个节点,起始节点的地址要指向第二个节点的地址。

代码如下: 

//链表头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* ptail = *pphead;SLTNode* next = (*pphead)->next;free(ptail);ptail = NULL;*pphead = next;
}

2.8 链表数据的查找 

//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}else{pcur = pcur->next;}}//找不到return NULL;
}

2.9 在指定位置之前插入数据

在指定节点之前插入一个新的节点,找到pos节点及其之前的节点prev,在两个节点之间插入新的节点。

代码如下: 

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* prev = *pphead;if (*pphead == pos){SLTPushFront(pphead, x);}else{while (prev->next != pos){prev = prev->next;}SLTNode* newnode = SLTBuyNode(x);	prev->next = newnode;newnode->next = pos;}
}

2.10 在指定位置之后插入数据

在指定位置之后(pos)插入新的节点,新节点存储pos下一个节点的地址。

代码如下: 

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}

 2.11 删除指定位置的节点

如果链表只有一个节点,直接调用头删函数;链表有多个节点,找到pos之前的节点和pos之后的节点,将它们连接起来。

代码如下: 

/删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);SLTNode* prev = *pphead;if (pos = *pphead){//头删SLTPopFront(pphead);}else{while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

2.12 删除pos之后的节点

创建一个临时标量del,提前存储好pos下一个节点的地址,然后先将pos存储好del后的地址,再释放del。

代码如下: 

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

2.13 链表的销毁

链表使用完后要进行销毁,将空间还给操作系统:

//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

3 链表实现完整代码

从头创建链表,并实现对链表的增删改查实现,我们需要创建一个头文件:SList.h,一个源文件SList.h,用来实现增删改查功能,一个测试文件test.c用来测试代码功能:

3.1 SList.h文件 

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型typedef struct SListNode//定义节点
{SLTDataType data;struct SListNode* next;
}SLTNode;//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x);
//打印链表
void SLTPrint(SLTNode* phead);
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//链表尾删
void SLTPopBack(SLTNode** pphead);
//链表头删
void SLTPopFront(SLTNode** pphead);
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

3.2 SList.c文件

#include "SList.h"//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}
//链表打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//创建新节点if (*pphead == NULL){*pphead = newnode;}else{//找尾SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//创建新节点newnode->next = *pphead;*pphead = newnode;
}
//链表尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead&&*pphead);if ((*pphead)->next == NULL)//链表如果只有一个节点{free(*pphead);*pphead = NULL;}else{SLTNode* ptail = *pphead;SLTNode* prev = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}
}
//链表头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* ptail = *pphead;SLTNode* next = (*pphead)->next;free(ptail);ptail = NULL;*pphead = next;
}
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}else{pcur = pcur->next;}}//找不到return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* prev = *pphead;if (*pphead == pos){SLTPushFront(pphead, x);}else{while (prev->next != pos){prev = prev->next;}SLTNode* newnode = SLTBuyNode(x);	prev->next = newnode;newnode->next = pos;}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);SLTNode* prev = *pphead;if (pos = *pphead){//头删SLTPopFront(pphead);}else{while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

3.3 test.c文件

#include "SList.h"
void SListTest01()
{//创建几个链表的节点测试SLTNode* node1=SLTBuyNode(1);SLTNode* node2=SLTBuyNode(2);SLTNode* node3=SLTBuyNode(3);SLTNode* node4=SLTBuyNode(4);//将节点链接起来node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;//链表打印测试/*SLTNode* plist = node1;SLTPrint(plist);*/}
void SListTest02()
{//链表尾插测试SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//链表头插测试//SLTPushFront(&plist, 5);//SLTPushFront(&plist, 6);//SLTPushFront(&plist, 7);//SLTPushFront(&plist, 8);//SLTPrint(plist);//链表尾删测试//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//链表头删测试//SLTPopFront(&plist);//SLTPrint(plist);//SLTPopFront(&plist);//SLTPrint(plist);//SLTPopFront(&plist);//SLTPrint(plist);//SLTPopFront(&plist);//SLTPrint(plist);//链表查找测试/*SLTNode* find=SLTFind(plist, 3);if (find == NULL){printf("找不到\n");}else{printf("找到了\n");}*///在指定位置之前插入数据测试//SLTNode* find = SLTFind(plist, 1);//SLTInsert(&plist,find, 11);//SLTPrint(plist);//在指定位置之后插入数据测试//SLTNode* find = SLTFind(plist, 1);//SLTInsertAfter(find, 11);//SLTPrint(plist);//删除pos节点测试//SLTNode* find = SLTFind(plist, 1);//SLTErase(&plist, find);//SLTPrint(plist);//find = SLTFind(plist, 2);//SLTErase(&plist, find);//SLTPrint(plist);//find = SLTFind(plist, 3);//SLTErase(&plist, find);//SLTPrint(plist);//find = SLTFind(plist, 4);//SLTErase(&plist, find);//SLTPrint(plist);//find = SLTFind(plist, 5);//SLTErase(&plist, find);//SLTPrint(plist);//删除pos之后的节点测试/*SLTNode* find = SLTFind(plist, 2);SLTEraseAfter(find);SLTPrint(plist);*///销毁链表测试SListDesTroy(&plist);}
int main()
{SListTest01();SListTest02();return 0;
}

以上就是有关单链表的所有内容了,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会不断地分享知识。

http://www.dtcms.com/a/284671.html

相关文章:

  • python(one day)——春水碧于天,画船听雨眠。
  • Python 网络爬虫 —— requests 库和网页源代码
  • 网络爬虫的介绍
  • Kafka 配置参数详解:ZooKeeper 模式与 KRaft 模式对比
  • 【Android】Span的使用
  • 深入了解linux系统—— 信号的捕捉
  • 卷积神经网络--网络性能提升
  • 如何成为高级前端开发者:系统化成长路径。
  • 初识 二叉树
  • BI Agent vs. 传统BI工具:衡石科技视角下的效率与智能跃迁
  • 亚远景科技助力长城汽车,开启智能研发新征程
  • AI产品经理面试宝典第34天:破解人机社交关系面试题与答法
  • 一台显示器上如何快速切换两台电脑主机?
  • 【vue-2】Vue 3 中的 v-on 指令:全面指南与最佳实践
  • 无线调制的几种方式
  • .NET Framework版本信息获取(ASP.NET探针),获取系统的.NET Framework版本
  • Axure设计设备外壳 - AxureMost 落葵网
  • 基于C#+SQlite开发(WinForm)个人日程管理系统
  • 通义万相-文生视频实践
  • 近期学习小结
  • DIDCTF-陇剑杯
  • MySQL安装(yum版)
  • 为什么使用时序数据库
  • 通用人工智能AGI遥遥无期,面临幻灭
  • C++11 ---- lambda表达式
  • 【图像处理基石】如何入门色彩评估?
  • 最大子段和,但是两段# Kadane
  • IMU噪声模型
  • 第五届建筑防水科技创新大会召开,凯伦股份再度入选科技创新企业十强榜单!
  • Pytorch深度学习框架实战教程03:Tensor 的创建、属性、操作与转换详解