单链表:数据结构的灵动之链

本文主要讲解链表的概念和结构以及实现单链表

目录

一、链表的概念及结构

二、单链表的实现

1.1链表的实现:

 1.2单链表的实现:

单链表尾插:

 单链表的头插:

 单链表的尾删:

 单链表头删:

 单链表查找:

 单链表在pos位置之后插入x:

单链表删除pos位置之后的值:

 在pos的前面插入:

  删除pos位置:

  销毁:

 整体实现:



一、链表的概念及结构

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

链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
⻋厢是独⽴存在的,且每节⻋厢都有⻋⻔。想象⼀下这样的场景,假设每节⻋厢的⻋⻔都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从⻋头⾛到⻋尾?
最简单的做法:每节⻋厢⾥都放⼀把下⼀节⻋厢的钥匙。

其中,下一节车厢的钥匙就是,下一个节点的地址,也就是下一个节点的指针

typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;

 next指针就相当于是下一节车厢的钥匙

链表中每一节车厢都是malloc独立申请下来的空间,我们称之为节点,节点主要由数据以及下一个节点的地址组成,所以我们要用结构体来封装

 当然,如果我们想要保存的数据是字符型或者是浮点型,只需要写一个,这个随时可以替换

typedef int SLTDateType;
  •  链式结构在逻辑上是连续的,在物理结构上不⼀定连续
  • 节点⼀般是从堆上申请的
  • 从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

 


二、单链表的实现

1.1链表的实现:

首先,创建链表,把链表连接起来

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDateType;
typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;
#include "slist.h"
void SListNodeTest()
{SListNode* node1 = (SListNode*)malloc(sizeof(SListNode));node1->data = 1;SListNode* node2 = (SListNode*)malloc(sizeof(SListNode));node2->data = 2;SListNode* node3 = (SListNode*)malloc(sizeof(SListNode));node3->data = 3;SListNode* node4 = (SListNode*)malloc(sizeof(SListNode));node4->data = 4;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SListPrint(node1);
}
int main()
{SListNodeTest();return 0;
}

 打印的函数:

void SListPrint(SListNode* plist)
{assert(plist);SListNode* pcur = plist;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

把连接起来的结果打印一下


 1.2单链表的实现:

  • 需要记住的两点:
  1. 但凡添了新数据第一步都是要申请新的空间的
  2. 插入节点,记住一定要从后往前改变

单列表相比于顺序表是没有初始化的,他一开始就是创建一个结构体指针

void SListNodeTest1()
{SListNode* plist = NULL;
}

单链表尾插:

  • 尾插首先需要找尾,但如果整个列表是空的的话,那就直接把newNode给*pphead,如果不是空的,就要找尾,定义一个ptail,不要动头节点*pphead,那ptail去尾插

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{SListNode* ptail = *pplist;SListNode* newNode=BuySListNode(x);if (ptail == NULL)*pplist = newNode;else{while (ptail->next){ptail = ptail->next;}ptail->next=newNode;}
}

 运行结果:

 这里传的是二级指针,为什么要传二级指针呢?

一开始定义了一个结构体的指针置为NULL,这个指针是plist等于NULL,我给这个结构体指针给他newNode空间和nest指针以及date数据,让 phlist==newNode,相当于定义了一个数组arr ,让int *tmp去开辟malloc空间,成功了就让arr==tmp,为了真正的改变这个SlistNode *phlist我要知道它地址,相当于说我要给它赋值,由于函数结束,你的形参是不能影响实参的,所以说我要传它的地址,他是一级指针,所以我要传二级指针,为了真正能赋值,让phlist接收好这个节点的空间, 这是给一级指针赋值,想要真正改变一级指针,,你就要传他的地址,就要用二级指针接收

 

 这是一个难点,头插也是一样

 单链表的头插:

  • 头插只需要让新节点newNode的next指针指向头节点*pplist,再让头节点*pplist到新的节点newNode上
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{SListNode* newNode = BuySListNode(x);newNode->next = *pplist;*pplist = newNode;
}

 单链表的尾删:

  • 尾删需要注意定义两个节点,一个节点跟着另一个节点的尾巴后面ptail2跟着ptail1的后面第一步就让ptail2=ptail1,让ptail1往后走,这样就让ptail2跟在ptail1后面,当找到最后个节点,直接把最后一个节点释放掉此时ptail1就是最后个节点,ptail2就是最后一个节点的前节点,那前一个节点的nest置为空
void SListPopBack(SListNode** pplist)
{assert(*pplist);SListNode* ptail1 = *pplist;SListNode* ptail2 = *pplist;while (ptail1->next){ptail2 = ptail1;ptail1 = ptail1->next;}free(ptail2->next);ptail2->next = NULL;
}

 单链表头删:

  • 头删只需要保存头节点的下一个支点,再让头节点释放掉,再让头节点走到刚才保存那个节点上去
void SListPopFront(SListNode** pplist)
{assert(*pplist);SListNode* pphead = (*pplist)->next;free(*pplist);*pplist = pphead;
}

 

 单链表查找:

  • 单链表的查找返回接收需要在Test.c上实现,找到了就返回,该节点找不到就返回空(NULL)
SListNode* SListFind(SListNode* plist, SLTDateType x)
{assert(plist);while (plist){if (plist->data == x)return plist;plist = plist->next;}return NULL;
}

 单链表在pos位置之后插入x:

  • 在pose位置之后插入x,只需要创建一个新的节点,让新节点的下一个节点指向pos下一个节点,再让pos下一个节点指向新节点
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{SListNode* newNode = BuySListNode(x);newNode->next = pos->next;pos->next = newNode;
}

 

单链表删除pos位置之后的值:
 

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

 在pos的前面插入:

  • 在pos的前面插入,首先,如果列表里面只有一个节点,那就相当于是头插,如果不是的话,那就循环找到pos
// 在pos的前面插入
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{assert(*pplist);if (*pplist == pos){SListPushFront(pplist, x);}else{SListNode* tmp = *pplist;while (tmp->next != pos){tmp = tmp->next;}SListNode* newNode = BuySListNode(x);newNode->next = pos;tmp->next = newNode;}
}

 

  删除pos位置:

  • 删除pos位置只需要用temp1保存pos的下一个节点再让pos释放掉,循环找到pos的前一个节点,再让pos前一个节点指向tmp1
 删除pos位置
void SLTErase(SListNode** pplist, SListNode* pos)
{SListNode* tmp = *pplist;SListNode* tmp1 = pos->next;while (tmp->next != pos){tmp = tmp->next;}free(pos);pos = NULL;tmp->next = tmp1;
}

  销毁:

  •  销毁需要一个一个销毁,因为每个节点都开辟了空间
void SLTDestroy(SListNode** pplist)
{assert(*pplist);while (*pplist){SListNode* tmp = (*pplist)->next;free(*pplist);*pplist = tmp;}
}

 整体实现:

slist.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDateType;
typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);//单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
//
// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x);删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos);
void SLTDestroy(SListNode** pphead);
slist.c
#include "slist.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{SListNode* Node = (SListNode*)malloc(sizeof(SListNode));if (Node == NULL){perror("malloc fail");exit(1);}Node->data = x;Node->next = NULL;return Node;}
// 单链表打印
void SListPrint(SListNode* plist)
{assert(plist);SListNode* pcur = plist;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{SListNode* ptail = *pplist;SListNode* newNode=BuySListNode(x);if (ptail == NULL)*pplist = newNode;else{while (ptail->next){ptail = ptail->next;}ptail->next=newNode;}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{SListNode* newNode = BuySListNode(x);newNode->next = *pplist;*pplist = newNode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{assert(*pplist);SListNode* ptail1 = *pplist;SListNode* ptail2 = *pplist;while (ptail1->next){ptail2 = ptail1;ptail1 = ptail1->next;}free(ptail2->next);ptail2->next = NULL;
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{assert(*pplist);SListNode* pphead = (*pplist)->next;free(*pplist);*pplist = pphead;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{assert(plist);while (plist){if (plist->data == x)return plist;plist = plist->next;}return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{SListNode* newNode = BuySListNode(x);newNode->next = pos->next;pos->next = newNode;
}单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{SListNode* tmp = pos->next->next;free(pos->next);pos->next = NULL;pos->next = tmp;
}// 在pos的前面插入
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{assert(*pplist);if (*pplist == pos){SListPushFront(pplist, x);}else{SListNode* tmp = *pplist;while (tmp->next != pos){tmp = tmp->next;}SListNode* newNode = BuySListNode(x);newNode->next = pos;tmp->next = newNode;}
}删除pos位置
void SLTErase(SListNode** pplist, SListNode* pos)
{SListNode* tmp = *pplist;SListNode* tmp1 = pos->next;while (tmp->next != pos){tmp = tmp->next;}free(pos);pos = NULL;tmp->next = tmp1;
}
void SLTDestroy(SListNode** pplist)
{assert(*pplist);while (*pplist){SListNode* tmp = (*pplist)->next;free(*pplist);*pplist = tmp;}
}

 ——————————————————完 结——————————————————————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38177.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

链表题型-链表操作-JS

一定要注意链表现在的头节点是空节点还是有值的节点。 一、移除链表中的元素 有两种方式&#xff0c;直接使用原来的链表进行删除操作&#xff1b;设置一个虚拟头节点进行删除操作。 直接使用原来的链表进行删除操作时&#xff0c;需要考虑是不是头节点&#xff0c;因为移除…

读《浪潮之巅》:探寻科技产业的兴衰密码

引言&#xff1a;邂逅《浪潮之巅》 在信息技术飞速发展的今天&#xff0c;科技公司如繁星般闪烁&#xff0c;又似流星般划过。而我与《浪潮之巅》的相遇&#xff0c;就像在浩渺的科技海洋中&#xff0c;发现了一座指引方向的灯塔。初次听闻这本书&#xff0c;是在一次技术交流会…

【和春笋一起学C++】文本文件I/O

在windows系统中读取键盘的输入和在屏幕上显示输出统称为&#xff1a;控制台输入/输出。把读取文本文件和把字符输出到文本文件中统称为&#xff1a;文本文件I/O。 目录 1. 输出文本文件 2. 读取文本文件 1. 输出文本文件 把字符输出到文本文件中和输出到控制台很相似&#x…

【C#】WinForm自定义控件及窗体

前言 WinForm&#xff08;Windows Forms&#xff09;是Microsoft.NET框架中的技术&#xff0c;用于开发Windows桌面应用程序。它提供了一套丰富的控件和组件。通过拖放控件、编写事件处理程序等方式快速构建用户界面。 通过属性窗口定制这些控件的外观和行为。 通过数据绑定&am…

Live555+Windows+MSys2 编译Androidso库和运行使用

下载 wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz tar -xzvf live555-latest.tar.gz加入版本控制 git init git add . git commit -a -m "first init" git log修改config.android-arm64 cd live vim config.android-arm64 ./genMakefile…

大模型-提示词工程与架构

什么是提示工程 提示工程&#xff08;Prompt Engineering&#xff09;是一门新兴的技术领域&#xff0c;专注于研究如何设计、构建和优化提示词&#xff0c;以充分发挥大模型的潜力 。它涉及到对语言结构、任务需求、模型特性等多方面因素的综合考量。提示工程的目标是通过精心…

Agent Team 多智能体系统解析

引言 在人工智能技术高速发展的今天&#xff0c;"多智能体协作系统"&#xff08;Agent Team&#xff09;正成为突破效率瓶颈的关键技术。与传统的单体AI不同&#xff0c;这种由多个专业化智能体组成的协同网络&#xff0c;通过分工协作和动态调整&#xff0c;展现出…

【蓝桥杯—单片机】IAP15F2K61S2专项 | 真题整理、解析与拓展 | 省赛题(更新ing...)

IAP15F2K61S2 专项 前言IAP15F2K61S2 介绍&#xff08;基于手册&#xff09;I/O口结构复位管脚RST中断第十四届省赛 外设通过PWM控制第十五届省赛题 性能与工作参数在线调试第十四届省赛题拓展与小结&#xff1a;单片机在线调试常用的接口 功耗第十五届省赛题 前言 在本文中我…

生物化学笔记:医学免疫学原理02 抗原概念+免疫应答+抗原的分类

抗原基本概念 影响抗原刺激机体产生免疫应答的因素 抗原的分类 CG 【北京大学】1080p 王月丹教授 《医学免疫学原理》2022春 全81p

(UI自动化测试)第二篇:元素定位的方法_name定位

二、name定位 ⽅法&#xff1a; driver.find_element_by_name(“name属性值”) 前置&#xff1a; 标签必须name属性 特点&#xff1a; 当前⻚⾯可以重复 提示&#xff1a; 由于name属性值可以重复&#xff0c;所以使⽤时需要查看是否为唯⼀。 # 导包selenium from selenium i…

软考中级-软件设计师 准备

软考中级-软件设计师 准备 一、软考相关1.1、考试时间1.2、考试时长1.3、题型和分值&#xff1a; 二、软考备考2.1、相关书籍2.2、推荐课程&#xff1a;B站up主zst_20012.3、学习路线 一、软考相关 1.1、考试时间 一年有两次软考&#xff0c;一般是五月末和十一月的中旬 以下…

记忆力训练day24

一 数字锁链串联法 数字两位 两位的连

田间机器人幼苗视觉检测与护苗施肥装置研究(大纲)

田间机器人幼苗视觉检测与护苗施肥装置研究 基于多光谱视觉与精准施肥的农业机器人系统设计 第一章 绪论 1.1 研究背景与意义 农业智能化需求&#xff1a; 传统幼苗检测依赖人工&#xff0c;效率低且易遗漏弱苗/病苗施肥不精准导致资源浪费和环境污染 技术挑战&#xff1a;…

Debian12生产环境配置笔记

在 Debian 12 上进行生产环境配置的详细步骤&#xff0c;涵盖软件更新、基础软件安装、Docker 及 Redis 部署&#xff0c;以及 Nginx 配置多个虚拟主机等内容。所有命令均以 root 用户身份执行&#xff0c;无需添加 sudo 1. 更新软件 首先&#xff0c;确保系统上的所有软件包…

HAL库编程知识点---Can.c和Driver_can.c分层开发

在一个工程中&#xff0c;通常会把对CAN外设的操作分成底层和上层两个部分&#xff0c;从而提高代码的模块化和可维护性。一般来说&#xff1a; can.c 通常由硬件抽象层&#xff08;HAL&#xff09;或者自动生成工具&#xff08;如 CubeMX&#xff09;提供或生成。主要负责CAN硬…

7. 【Vue实战--孢子记账--Web 版开发】-- 收支分类设置

本篇文章我们一起来实现收支分类功能。收支分类和前篇文章的主币种设置界面大体类似。我们将详细介绍如何创建和管理不同的收支分类&#xff0c;以便用户可以更好地组织和跟踪他们的财务状况。 一、功能 先来看一下原型界面&#xff0c;界面很简单&#xff0c;这里就不多讲解…

人工智能 - DeepSeek 和 Manus 的区别和应用场景

DeepSeek 与 Manus 是人工智能领域两种不同技术路线的代表,其核心区别在于功能定位和技术实现,应用场景也因此存在显著差异。以下是两者的对比分析: 一、核心区别 技术定位 DeepSeek:定位为“超级大脑”,专注于底层大模型的研发,擅长处理数学题、代码生成、知识问答等需要…

基于yolov11的防震锤缺陷检测系统python源码+pytorch模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv11的防震锤缺陷检测系统是一种利用深度学习技术进行自动化检测的系统。防震锤是电力线路中用于防止导线因风力等因素引起振动的关键部件&#xff0c;其性能状态直接影响到电力线路的安全运行。然而&#xff0c;防震锤在使用过程中可能会因各种因素导致缺…

MySQL数据库精研之旅第二期:库操作的深度探索

专栏&#xff1a;MySQL数据库成长记 个人主页&#xff1a;手握风云 目录 一、查看数据库 二、创建数据库 2.1. 语法 2.2. 示例 三、字符集编码和校验(排序)规则 3.1. 查看数据库支持的字符集编码 3.2. 查看数据库支持的排序规则 3.3. 不同的字串集与排序规则对数据库的…

ubuntu系统/run目录不能执行脚本问题解决

目录 前言 一、问题现象 二、原因分析 三、解决办法 总结 前言 在使用 Ubuntu 系统的过程中&#xff0c;我们有时会遇到在 /run 目录下无法执行脚本的情况。这篇博客将详细探讨该问题的原因&#xff0c;并提供有效的解决方案。。 一、问题现象 当尝试在 /run 目录下执行一个…