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

【数据结构七夕专属版】单链表及单链表的实现【附源码和源码讲解】

本篇是博主在学习数据结构时的心得,希望能够帮助到大家,也许有些许遗漏,但博主已经尽了最大努力打破信息差,如果有遗漏还请见谅,嘻嘻,前路漫漫,我们一起前进!!!!

今天是七夕!!!虽然博主没有女朋友,但是在此我也祝各位有情人终成眷属。爱永无止境。 

希望你们都能遇到自己的米子哈和大kin库!!!!!!!!!!!!!!!!!!!!!!! 

 


目录

 1.单链表的简介

1.1结点

结点申请规则 

单链表和顺序表的对比 

2、单链表的实现及其对应源码:

2.1单链表的创建:

 2.2.链表的申请结点

 2.3.单链表的打印

2.4.链表的尾插

2.5. 链表的头插

2.6.链表的尾删

2.7.链表的头删

2. 8.链表的查找

2.9. 链表的指定位置前的结点插入

2.10. 在指定位置后插入结点

2.11.删除pos结点

2.12.销毁链表

 1.单链表的简介

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

1.1结点

链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

结点申请规则 

  1. 链式机构在逻辑上是连续的,在物理结构上不⼀定连续
  2. 结点⼀般是从堆上申请的
  3. 从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续  

单链表申请空间:

struct SListNode
{
int data;
SListNode*next;
}

在我们想要存储一个整形数据时,实际先向内存申请一块空间,这个空间不仅要存放当前结点的数据,还存放着下一个结点的地址。(最后的结点存储的地址为NULL) 


单链表和顺序表的对比 

  1. 存储结构:
    单链表的存储结构是非连续,非顺序的依靠地址的来寻找链表中的下一位数据。
    顺序表在物理存储结构上是连续的,内存读取依次访问数据元素。
  2. 逻辑顺序:
    单链表的逻辑顺序是依靠结点相连接的,
    顺序标的逻辑顺序和物理顺序一样是紧挨着的。
     
  3. 代码实现:
    单链表的实现需要依靠指针
    顺序表可以不用指针,用访问符就能访问到数据
     
  4. 数据的存储:
    在顺序表中,只需要开辟一系列的相邻的一块空间进行数据的存储。
    在单链表中,每一个数据的位置都要申请,申请下来的空间叫做“结点”。

图解


2、单链表的实现及其对应源码:

单链表包括创建、申请结点、打印、头插、尾插、头删、尾删、链表查找、指定前插入、指定后插入、删除pos结点、删除pos后结点、销毁链表

单链表的实现 :

2.1单链表的创建:

typedef int SLDataType
typedef struct SListNode
{
SLDataType  Data;
struct SListNode*next;
}sL;

以上代码是对链表结点结构的声明,该链表结点包括一个类型为SLDataType的整形数据一个地址。


 

 2.2.链表的申请结点

SL*SLTButNode(SLTDataType x)
{
SL*newnode=(SL*)malloc(sizeof(SL));
if(newnode==NULL)
{
perror("malloc fail");
exit(1);
}
newnode->data=x;
newnode->next=NULLl;
}

我们用图表示一下:


 2.3.单链表的打印

void SLprint(SL*phead)
{
SL*pucr=phead;
while(pcur)
{
printf("%d",pucr->next);}
printf("NULL/n");}

 这下我们用图解:

 这里有几个注意的点:

1.*phead一定是该链表中的第一个结点。

2.对pcur解引用拿到下一个结点的指针才能进入下一次循环,不然pcur走不动,打印陷入死循环。

2.4.链表的尾插

void SLTPushBack(SL**pphead,SLTDataType x)
{
assert(pphead);
SL*newnode=SLButNode(x);
if(*pphead==NULL)
{
*pphead=newnode;}
else
{SL*pcur=*pphead;
while(pcur->next)
{
pucr=pucr->next;
}
pcur->next=newnode;}

如果*pphead是NULL时说明该链表的头结点还没有建立,所以如果从对一个没有头节点的链表进行尾插,头节点就是尾插的目标。

当*pphead不是NULL的时代表要在链表结点之后插入,为了更直观的感受,我们用图解的方式来解释:


2.5. 链表的头插

void SLTPushFront(SL**pphead,SLDataType x)
{
assert(pphead&&*pphead);
SL*newnode=SLBuynode(x);newnode->next=*pphead;
*pphead=newnode;}

头插相较于尾插简单的多:

只要将先将头结点赋值给新的结点的存储地址,然后在再将新节点newnode当作新的头结点*pphead即可; 


2.6.链表的尾删

void SLTpopBack(SL**pphead)
{
//链表为空:不可以删除
assert(pphead && *pphead);
//处理只有一个结点的情况:要删除的就是头结点
if((*pphead)->next=NULL)
{
free(*pphead);
*pphead=NULL;
}
else
{
SL* ptail = *pphead;
SL* prev = NULL;
while (ptail->next)
{prev = ptail;ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;}

这里我们还是用图解的方式来讲解:

尾删详解:这里用prev作为ptail的前一个指针,当ptail找到最后一个结点的时候,prev就是尾删的尾结点,所以先将prev的存储的下一个结点地址(prev->next)置为NULL,再将ptail所指的空间释放,最后ptail指向NULL,这就是尾删的过程

 


2.7.链表的头删

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SL* next = (*pphead)->next;//*pphead --> nextfree(*pphead);*pphead = next;
}

这里头删就更加简单了,头结点就是要删除的结点,直接将头结点的指向的下一个结点的地址存储到next中,在进行释放头结点空间,最后在对新结点进行操作


2. 8.链表的查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}

查找详解:

  1. 我们用pcur指针对单链表进行遍历,遍历过程中如果找到是否有与x匹配的数据,说明找到了,返回与x相等数据的pcur指针。
  2. 用pcur遍历完之后,如果没有找到与之匹配的数据,说明没有找到,返回NULL 。


2.9. 链表的指定位置前的结点插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* newnode = SLTBuyNode(x);//找prev :pos的前一个结点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev newnode --> posnewnode->next = pos;prev->next = newnode;}
}

插入(位置前)详解:

  1. 如果pos的位置在头结点,我们直接在头结点头插即可。
  2. 如果pos不在头结点,我们先创建一个新的结点,用prev指针找到pos的前一个结点的地址,pos的本质是一个地址,我们用将新节点存储的地址给pos,newnode的地址给到prev指针所指向结点的存储地址。这样我们就将新节点插入到了pos结点的前面 


2.10. 在指定位置后插入结点

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//pos newnode --> pos->nextnewnode->next = pos->next;pos->next = newnode;
}

插入(位置后)详解:

在指定位置后插入数据不需要用循环找pos后的位置了,因为pos所在的结点存储的地址就是下一个结点的地址,所以将pos存储的下一个结点的地址赋值给newnode的存储地址之后再将pos的存储地址给newnode的地址,这样就完成了位置后插入。


2.11.删除pos结点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//头删if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);//pos pos->next pos->next->nextSLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

删除pos结点详解: 

删除pos结点:用prev指针找到pos前一个结点的位置,用prev所在的结点的存储地址改为pos->next(pos后一个结点)之后释放pos结点的空间,最后将pos指针置为NULL

删除pos之后的结点:用del代表pos的下一个结点,将pos的下下个结点的地址给pos的存储地址,释放del,将del指针置为NULL;


2.12.销毁链表

void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

超详细源码:

cpp文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"
void SLprint(SLTNode*phead)
{SLTNode* pcur = phead;while (pcur){printf("%d", pcur->data);}printf("NULL/n");}
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if(newnode==NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (pphead == NULL){*pphead = newnode;}else{//找尾结点SLTNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}//pcur  newnodepcur->next = newnode;
n	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//newnode *ppheadnewnode->next = *pphead;*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{//链表为空:不可以删除assert(pphead && *pphead);//处理只有一个结点的情况:要删除的就是头结点	if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找 prev ptailSLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;}}void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;//*pphead --> nextfree(*pphead);*pphead = next;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* newnode = SLTBuyNode(x);//找prev :pos的前一个结点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev newnode --> posnewnode->next = pos;prev->next = newnode;}
}
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//pos newnode --> pos->nextnewnode->next = pos->next;pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//头删if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);//pos pos->next pos->next->nextSLTNode* 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;
}

.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//定义链表(结点)的结构typedef int SLTDataType;typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;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);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);


结束语: 

本篇单链表的章节到这里就结束了..........

感谢各位能观看到这里,感谢大家支持博主,很开心能和大家一起共同进步,一起学习!!!!!我希望能让更多的人看到这篇文章,希望大家能够多点赞,多交流。可以点波收藏,忘记的时候可以观看,在此wheel down先谢过大家!!!!!
 

时间漫长,我们一起度过,前路未知,我们一起并肩。 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【SpringBoot系列】接口参数的默认值与必要性
  • 服务器HTTP响应头安全性优化与漏洞修复方案
  • Selenium + Python 自动化测试09(多窗口切换)
  • 白骑士的PyCharm教学实战项目篇 4.4 大数据处理与分析
  • CRC32 JAVA C#实现
  • 数据库国产化大趋势下,还需要学习Oracle吗?
  • 原神4.8版本升级计划数据表
  • 55.跳跃游戏
  • ubuntu中设置开机自动运行的(sudo)指令
  • 暑期数据结构 时间复杂度
  • C#获取Network的相关信息
  • 招聘求职小程序
  • github技巧和bug解决方法短篇收集
  • 【Impala】学习笔记
  • Android之复制文本(TextView)剪贴板
  • 《深入 React 技术栈》
  • 【刷算法】求1+2+3+...+n
  • IDEA常用插件整理
  • java中具有继承关系的类及其对象初始化顺序
  • LeetCode18.四数之和 JavaScript
  • mongodb--安装和初步使用教程
  • passportjs 源码分析
  • PAT A1050
  • python大佬养成计划----difflib模块
  • Redash本地开发环境搭建
  • Redux系列x:源码分析
  • v-if和v-for连用出现的问题
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • - 概述 - 《设计模式(极简c++版)》
  • 机器学习学习笔记一
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 聊聊hikari连接池的leakDetectionThreshold
  • 码农张的Bug人生 - 初来乍到
  • 如何利用MongoDB打造TOP榜小程序
  • 数据结构java版之冒泡排序及优化
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • gunicorn工作原理
  • ​iOS实时查看App运行日志
  • ​queue --- 一个同步的队列类​
  • # 飞书APP集成平台-数字化落地
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • #if和#ifdef区别
  • $(function(){})与(function($){....})(jQuery)的区别
  • (C语言)字符分类函数
  • (Windows环境)FFMPEG编译,包含编译x264以及x265
  • (亲测有效)推荐2024最新的免费漫画软件app,无广告,聚合全网资源!
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)可以带来幸福的一本书
  • (转)四层和七层负载均衡的区别
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .JPG图片,各种压缩率下的文件尺寸
  • .Net - 类的介绍
  • .NET : 在VS2008中计算代码度量值
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET Core引入性能分析引导优化