数据结构:单链表

devtools/2024/9/22 17:28:46/

目录

链表的概念

链表

举个事例:

代码实现

实现功能(代码的解析全在注释中)

申请内存

尾部插入

头部插入

尾部删除

头部删除

查找

在指定位置之前插⼊数据 

在指定位置之后插⼊数据 

删除pos节点 

删除pos之后的节点 

打印

销毁

完整代码

text.h

text.c


链表的概念

        链表是一种基本的数据结构,它用于存储一系列元素(节点),每个节点不仅包含数据元素,还包含一个指向下一个节点的指针。在链表中,数据并非连续地存储在内存中,而是通过每个节点的指针链接起来形成一个逻辑上的线性序列

通过前面我们学习的顺序表我们现在延伸一个链表我们会发现顺序表的一些缺点

顺序表相比链表的主要缺点在于:

  1. 插入和删除操作效率低,可能需要移动大量元素;
  2. 内存空间利用率不高,预先分配的固定容量可能导致浪费或不足;
  3. 不易实现动态扩展,扩容成本高;
  4. 对连续内存空间需求较大,不易满足大规模数据存储需求;
  5. 虽然支持随机访问,但在物理存储地址发生变化时,如扩容导致地址变动,维持引用的稳定性较链表更为复杂。

链表

        让我们一起分析这个示意图:phead 是一个指向链表头节点的指针,通过它,我们可以获取到第一个节点的地址(例如:0x00001)。借助这个地址,我们就能访问到链表的第一个节点。在该节点中,有两个关键部分,一是用于存储数据内容的元素(如 date),另一个是指向下个节点的指针(即 next 指针)。通过不断跟随 next 指针,我们就可以遍历整个链表

举个事例:

        假设phead是一家火锅店的迎宾员手中的“引路牌”,它直接指向了店里的第一个包厢(想象成链表中的第一个节点,地址为0x00001)。进入这个包厢后,我们会发现里面有两个重要组成部分:一是包厢内享用的美食(对应于链表节点中存储的数据date),二是通往下一个包厢的门(相当于指向下一个节点的next指针)。如此一来,只要通过这扇“门”,我们就可以依次拜访火锅店的所有包厢(即遍历整个链表)。

通过上面的解释我们就可以写出这样的代码:

//定义链表节点结构
typedef struct SListNode
{SLTDataType data;//内容struct SListNode* next;//指向下一个节点的指针
}SLTNode;

代码实现

首先我们看看单链表需要的一些功能

typedef int SLTDataType;
//定义链表节点结构
typedef struct SListNode
{SLTDataType data;//内容struct SListNode* next;//指向下一个节点的指针
}SLTNode;//打印
void SLTPrint(SLTNode** Phead);//尾部插入/头部插入
void Tail_insertion(SLTNode** Phead, SLTDataType x);
void  Head_insertion(SLTNode** Phead, SLTDataType x);//尾部删除/头部删除
void Tail_delete(SLTNode** Phead);
void Head_delete(SLTNode** Phead);//查找
SLTNode* Find(SLTNode* Phead, SLTDataType x);//在指定位置之前插⼊数据 
void SLTInsert(SLTNode** Phead, SLTNode* pos, SLTDataType x);//在指定位置之后插⼊数据 
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos节点 
void SLTErase(SLTNode** Phead, SLTNode* pos);//删除pos之后的节点 
void SLTEraseAfter(SLTNode* pos);//销毁链表 
void SListDesTroy(SLTNode** Phead);

实现功能(代码的解析全在注释中)

申请内存


//开辟内存函数
static SLTNode* SList_ina(SLTDataType x) {//申请空间SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//判断是否为空if (newnode == NULL){perror("malloc");exit(EXIT_FAILURE);//中止程序}newnode->data = x;//将新节点的内容x赋值newnode->next = NULL;//将指向下一个地址设NULLreturn newnode;//返回节点
}

尾部插入

// 函数功能:在单链表尾部插入新元素x
// 输入参数:
//   Phead:指向链表头结点的指针的指针
//   x:要插入的新元素值
void Tail_insertion(SLTNode** Phead, SLTDataType x) {// 如果链表为空,则新建节点作为头节点if (*Phead == NULL) {*Phead = SList_ina(x); // 调用SList_ina函数创建并初始化新节点} else {// 链表非空时,创建一个指针pcur指向头节点SLTNode* pcur = *Phead;// 遍历链表直至找到尾节点while (pcur->next != NULL) {pcur = pcur->next;}// 在尾节点后面插入新节点pcur->next = SList_ina(x); // 创建并初始化新节点,连接到当前尾节点之后}
}

头部插入

//头插
void  Head_insertion(SLTNode** Phead, SLTDataType x) {//创建新的节点SLTNode* newcode = SList_ina(x);//将新开辟的节点的执政指向第一个节点newcode->next = *Phead;//由于Phead指向的是头部,然后我们在头部插入了一个节点,这个时候Phead指向的就不再是头部,我们直接将新节点的地址赋值给这个指向头部的指针*Phead = newcode;
}

尾部删除

//尾删
void Tail_delete(SLTNode** Phead) {assert(Phead && *Phead);//判断是否是一个节点//如果是就直接释放掉//如果不是就找的尾节点,和为节点的上一个节点if ((*Phead)->next == NULL)//->优先级高于*{free(*Phead);*Phead = NULL;}else{//定义两个指针都指向头节点SLTNode* Tail_1 = *Phead;SLTNode* Tail_2 = *Phead;//当Tail_1 == NULL的时候我们的while循环就跳出去了,这样我们的Tail_2就存的尾节点的上一个位置while (Tail_1 ->next){Tail_2 = Tail_1;//存储尾节点的上一个位置Tail_1 = Tail_1->next;//找尾节点}free(Tail_1);Tail_1 = NULL;Tail_2->next = NULL;//要将指向的下一个元素设为NULL}
}

头部删除

//头删
void Head_delete(SLTNode** Phead) {assert(*Phead && Phead);//Phead -- 不能解引用空指针  *Phead -- 指向第一个节点的指针,第一个节点不能为空//存储第二个节点的地址SLTNode* next = (*Phead)->next;//直接释放掉第一个节点free(*Phead);//将我们存储的第二个的地址赋给*Phead(头指针),让他指向第二个节点,让第二个节点变成第二个节点*Phead = next;
}

查找

//查找
SLTNode *Find(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** Phead, SLTNode* pos, SLTDataType x) {assert(Phead && *Phead);SLTNode* pcur = *Phead;//把第一个节点地址传给pcurSLTNode* newnode = SList_ina(x);SLTNode* poss =  Find(*Phead,pos);if (*Phead == poss){Head_insertion(Phead,x);}else{//寻找pos前的一个点while (pcur->next != poss){assert(pcur->next);//判断下一个节点不能为空pcur = pcur->next;}//改变节点指针newnode->next = poss;pcur->next = newnode;}
}

在指定位置之后插⼊数据 

//在指定位置之后插⼊数据 
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos && pos->next);SLTNode* newnode = SList_ina(x);SLTNode* pcur = pos->next;//将下一个节点地址给pcurnewnode->next = pcur;pos->next = newnode;
}

删除pos节点 

//删除pos节点 
void SLTErase(SLTNode** Phead, SLTNode* pos) {assert(Phead && *Phead);if (*Phead == pos)//要删除第一个节点{//直接调用头删除Head_delete(Phead);}else{//将第一个节点地址传入pcurSLTNode* pcur = *Phead;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;//将pos中next指针指向的地址让pcur中next指针指向//释放posfree(pos);pos = NULL;}
}


删除pos之后的节点 

//删除pos之后的节点 
void SLTEraseAfter(SLTNode* pos){assert(pos && pos->next);SLTNode* pcur = pos->next;//将第二个节点的地址存在pcurpos->next = pcur->next;//这边pcur->next指向的是第三个节点的地址free(pcur);pcur = NULL;
}

打印

//打印
void SLTPrint(SLTNode** Phead) {assert(Phead);SLTNode* pcur = *Phead;while (pcur){printf("%d->", pcur->data);//打印节点内容pcur = pcur->next;//下一个节点地址赋值(循环)}printf("NULL\n");
}

销毁

//销毁链表
void SListDesTroy(SLTNode** Phead) {//这里的销毁我们需要将每一个节点都销毁掉assert(Phead && *Phead);SLTNode* pcur = *Phead;while (pcur){SLTNode* next = pcur->next;//将下一个位置存储一下free(pcur);pcur = next;}*Phead = NULL;
}

完整代码

text.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<vld.h>typedef int SLTDataType;
//定义链表节点结构
typedef struct SListNode
{SLTDataType data;//内容struct SListNode* next;//指向下一个节点的指针
}SLTNode;//打印
void SLTPrint(SLTNode** Phead);//尾部插入/头部插入
void Tail_insertion(SLTNode** Phead, SLTDataType x);
void  Head_insertion(SLTNode** Phead, SLTDataType x);//尾部删除/头部删除
void Tail_delete(SLTNode** Phead);
void Head_delete(SLTNode** Phead);//查找
SLTNode* Find(SLTNode* Phead, SLTDataType x);//在指定位置之前插⼊数据 
void SLTInsert(SLTNode** Phead, SLTNode* pos, SLTDataType x);//在指定位置之后插⼊数据 
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos节点 
void SLTErase(SLTNode** Phead, SLTNode* pos);//删除pos之后的节点 
void SLTEraseAfter(SLTNode* pos);//销毁链表 
void SListDesTroy(SLTNode** Phead);

text.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"//开辟内存函数
static SLTNode* SList_ina(SLTDataType x) {//申请空间SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//判断是否为空if (newnode == NULL){perror("malloc");exit(EXIT_FAILURE);//中止程序}newnode->data = x;//将新节点的内容x赋值newnode->next = NULL;//将指向下一个地址设NULLreturn newnode;//返回节点
}//打印
void SLTPrint(SLTNode** Phead) {assert(Phead);SLTNode* pcur = *Phead;while (pcur){printf("%d->", pcur->data);//打印节点内容pcur = pcur->next;//下一个节点地址赋值(循环)}printf("NULL\n");
}//尾插
void Tail_insertion(SLTNode** Phead, SLTDataType x){//判断链表是否为空//开辟新的节点SLTNode* newnode = SList_ina(x);if (*Phead == NULL){*Phead = newnode;}else{//用一个指针指向第一个元素SLTNode* pcur = *Phead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}//头插
void  Head_insertion(SLTNode** Phead, SLTDataType x) {//创建新的节点SLTNode* newcode = SList_ina(x);//将新开辟的节点的执政指向第一个节点newcode->next = *Phead;//由于Phead指向的是头部,然后我们在头部插入了一个节点,这个时候Phead指向的就不再是头部,我们直接将新节点的地址赋值给这个指向头部的指针*Phead = newcode;
}//尾删
void Tail_delete(SLTNode** Phead) {assert(Phead && *Phead);//判断是否是一个节点//如果是就直接释放掉//如果不是就找的尾节点,和为节点的上一个节点if ((*Phead)->next == NULL)//->优先级高于*{free(*Phead);*Phead = NULL;}else{//定义两个指针都指向头节点SLTNode* Tail_1 = *Phead;SLTNode* Tail_2 = *Phead;//当Tail_1 == NULL的时候我们的while循环就跳出去了,这样我们的Tail_2就存的尾节点的上一个位置while (Tail_1 ->next){Tail_2 = Tail_1;//存储尾节点的上一个位置Tail_1 = Tail_1->next;//找尾节点}free(Tail_1);Tail_1 = NULL;Tail_2->next = NULL;//要将指向的下一个元素设为NULL}
}//头删
void Head_delete(SLTNode** Phead) {assert(*Phead && Phead);//Phead -- 不能解引用空指针  *Phead -- 指向第一个节点的指针,第一个节点不能为空//存储第二个节点的地址SLTNode* next = (*Phead)->next;//直接释放掉第一个节点free(*Phead);//将我们存储的第二个的地址赋给*Phead(头指针),让他指向第二个节点,让第二个节点变成第二个节点*Phead = next;
}//查找
SLTNode *Find(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** Phead, SLTNode* pos, SLTDataType x) {assert(Phead && *Phead);SLTNode* pcur = *Phead;//把第一个节点地址传给pcurSLTNode* newnode = SList_ina(x);SLTNode* poss =  Find(*Phead,pos);if (*Phead == poss){Head_insertion(Phead,x);}else{//寻找pos前的一个点while (pcur->next != poss){assert(pcur->next);//判断下一个节点不能为空pcur = pcur->next;}//改变节点指针newnode->next = poss;pcur->next = newnode;}
}//在指定位置之后插⼊数据 
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos && pos->next);SLTNode* newnode = SList_ina(x);SLTNode* pcur = pos->next;//将下一个节点地址给pcurnewnode->next = pcur;pos->next = newnode;
}//删除pos节点 
void SLTErase(SLTNode** Phead, SLTNode* pos) {assert(Phead && *Phead);if (*Phead == pos)//要删除第一个节点{//直接调用头删除Head_delete(Phead);}else{//将第一个节点地址传入pcurSLTNode* pcur = *Phead;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;//将pos中next指针指向的地址让pcur中next指针指向//释放posfree(pos);pos = NULL;}
}//删除pos之后的节点 
void SLTEraseAfter(SLTNode* pos){assert(pos && pos->next);SLTNode* pcur = pos->next;//将第二个节点的地址存在pcurpos->next = pcur->next;//这边pcur->next指向的是第三个节点的地址free(pcur);pcur = NULL;
}//销毁链表
void SListDesTroy(SLTNode** Phead) {//这里的销毁我们需要将每一个节点都销毁掉assert(Phead && *Phead);SLTNode* pcur = *Phead;while (pcur){SLTNode* next = pcur->next;//将下一个位置存储一下free(pcur);pcur = next;}*Phead = NULL;
}


http://www.ppmy.cn/devtools/4118.html

相关文章

Ubuntu设置中文输入法教程

在Ubuntu上设置中文输入法很简单&#xff0c;你可以按照以下步骤进行操作&#xff1a; 打开系统设置 点击桌面左上角的"Activities"图标&#xff0c;然后在搜索栏中输入"Settings"&#xff0c;点击打开系统设置。 进入区域与语言设置 在系统设置中&#x…

fastapi写一个上传的接口

首先&#xff0c;确保您已经在 Python 环境中安装了 FastAPI。 安装环境&#xff1a; pip install fastapi uvicorn让我们创建一个图片上传的接口&#xff1a; from fastapi import FastAPI, File, UploadFile from fastapi.responses import JSONResponse import shutil im…

java的volatile

在Java中&#xff0c;线程之间对内存写入操作的可见性是一个重要的问题&#xff0c;因为每个线程都有自己的工作内存&#xff0c;并且线程之间共享主内存。当一个线程修改了共享变量的值&#xff0c;其他线程并不一定能立即看到这个修改&#xff0c;这就是所谓的可见性问题。 例…

汇编语言——将BX中的无符号数和有符号数以二进制、八进制、十六进制、十进制形式输出

文章目录 将BX中的无符号数以二进制形式输出将BX中的无符号数以八进制形式输出将BX中的无符号数以十六进制形式输出将BX中的无符号数以十进制形式输出将BX中的有符号数以十进制形式输出 将BX中的无符号数以二进制形式输出 利用移位指令会影响CF&#xff0c;默认dl30h(数字0)&a…

牛客周赛 Round 39(补题)

小红不想做完全背包 &#xff08;hard&#xff09; 题目描述 本题和easy版本的唯一区别是&#xff1a;ppp没有必须等于3的限制。 完全背包是一个经典问题&#xff0c;但小红完全不会完全背包&#xff0c;因此她不想做完全背包。 现在小红拿到了一个长的很像完全背包的题&…

Freertos学习第二天-Freertos基于ESP32-给任务传递单个参数

一共有两种方法 第一种: 在创建任务中可以传递参数&#xff0c;void *pt 传递了一个空指针 void Task1(void *pt) 可以运用这个空指针来设置引脚 byte * pbLED1PIN;pbLEDPIN &LED1_PIN;void * pvLED1PIN;pvLED1PIN (void *)pbLED1PIN; 以上代码意思是 byte * pbLE…

计算机网络——GBN协议实现

实验目的 编程模拟实现GBN可靠传输软件 实验内容 C 程序模拟实现Go-Back-N可靠数据传输&#xff0c;需要编写一个发送端程序和一个测试端程序来模拟传输过程 具体流程 1. 编写发送端程序&#xff0c;调用库实现socket连接&#xff0c;然后主要实现滑动窗口&#xff0c;接收…

神经网络压缩图像

简介 典型的压缩管道由四个组件组成&#xff1a; 编码&#xff1a;输入图像 x x x通过编码器函数 ε \varepsilon ε&#xff0c;将其转换为潜在表示 z z z。 量化&#xff1a;截断 z z z以丢弃一些不重要的信息 熵编码&#xff1a;使用某种形式的熵编码&#xff08;例如&…