数据结构——单链表专题
目录
- 1. 链表的概念及结构
- 2. 实现单链表
- 初始化
- 尾插
- 头插
- 尾删
- 头删
- 查找
- 在指定位置之前插入数据
- 在指定位置之后插入数据
- 删除指定位之前的节点
- 删除指定位置之后pos节点
- 销毁链表
- 3. 完整代码
- test.c
- SList.h
- 4. 链表的分类
1. 链表的概念及结构
在顺序表中存在一定的问题:
- 顺序表的中间/头部插入、删除需要挪动数据
- 扩容存在性能的消耗
- 会有空间的浪费
而链表是独立的节点组成
链表概念:链表是一种物理存储结构上非连续、非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表、顺序表都是线性表
逻辑结构一定是线性的
物理结构不一定是线性的
图中指针变量plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0
为什么还需要指针变量来保存下一个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
链表是由一个一个的节点组成的
一个节点由两部分组成:要存储的数据+指针
int a = 1;
int* pa = &a;
节点结构的定义
struct SListNode{
int data;
struct SListNode* next;
}
链表结构体的打印方式:
补充说明:
- 链式机构在逻辑上是连续的,在物理结构上不一定连续
- 节点一般是从堆上申请的
- 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续
2. 实现单链表
初始化
typedef int SLTDataType;
//链表是由节点组成的
typedef struct SListNode
{SLTDataType data;//节点数据struct SListNode* next;//指针变量保存下一个节点的地址
}SLTNode;
尾插
//公共部分,开辟一个新的节点
SLTNode* SLBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLBuyNode(x);//链表为空,新节点作为pheadif (*pphead == NULL){*pphead = newnode;return;}//链表不为空,找尾结点SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//ptril就是尾结点,将尾结点指向newnodeptail->next = newnode;
}
头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLBuyNode(x);//newnode *ppheadnewnode->next = *pphead;*pphead = newnode;
}
尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//链表不能为空assert(*pphead);//指向第一个节点的地址//链表不为空//链表只有一个节点/多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* 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);//链表不能为空assert(*pphead);//让第二个节点称为新的头SLTNode* next = (*pphead)->next;//->优先级高于*//把旧的头节点释放掉free(*pphead);*pphead = next;
}
查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(*pphead);//遍历链表SLTNode* pcur = *pphead;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);//链表不能为空assert(*pphead);//pos刚好是头结点if (pos == *pphead){//头插SLTPushFront(pphead, x);return;}//pos不是头结点的情况SLTNode* newnode = SLBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//找到了prev->next = newnode;newnode->next = pos;
}
在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
删除指定位之前的节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead);assert(pos);//pos刚好是头结点,没有前驱节点,执行头删if (*pphead == pos){//头删SLTPopFront(pphead);return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
删除指定位置之后pos节点
//删除指定位置之后pos节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pos->next);//pos pos->next pos->next->nextSLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}}
销毁链表
//销毁链表
void SListDesTory(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
3. 完整代码
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
#include <stdio.h>//void SlistTest1()
//{
// //一般不会这样去创建链表,这里只是为了给大家展示链表的打印
// SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
// node1->data = 1;
// SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
// node2->data = 2;
// SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
// node3->data = 3;
// SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
// node4->data = 4;
//
// node1->next = node2;
// node2->next = node3;
// node3->next = node4;
// node4->next = NULL;
//
// SLTNode* plist = node1;//定一个指针指向第一个节点
// SLTPrint(plist);
//}void SlistTest2()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 0);SLTPrint(plist);//SLTPushFront(&plist, 2);//SLTPushFront(&plist, 3);//SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}void SlistTest3()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 0);SLTPrint(plist);//头删SLTPopFront(&plist);SLTPrint(plist);//查找SLTNode* FindRet = SLTFind(&plist, 0);if (FindRet){printf("找到了\n");}else {printf("未找到\n");}SLTInsert(&plist, FindRet, 100);SLTPrint(plist);SLTInsertAfter(FindRet, 200);SLTPrint(plist);//删除指定位置的节点SLTErase(&plist, FindRet);SLTPrint(plist);//销毁节点SListDesTory(&plist);
}int main()
{SlistTest3();//SlistTest2();//SlistTest1();return 0;
}
SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
#include <assert.h>//链表的打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}SLTNode* SLBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLBuyNode(x);//链表为空,新节点作为pheadif (*pphead == NULL){*pphead = newnode;return;}//链表不为空,找尾结点SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//ptril就是尾结点,将尾结点指向newnodeptail->next = newnode;
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLBuyNode(x);//newnode *ppheadnewnode->next = *pphead;*pphead = newnode;
}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//链表不能为空assert(*pphead);//指向第一个节点的地址//链表不为空//链表只有一个节点/多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* 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);//链表不能为空assert(*pphead);//让第二个节点称为新的头SLTNode* next = (*pphead)->next;//->优先级高于*//把旧的头节点释放掉free(*pphead);*pphead = next;
}//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(*pphead);//遍历链表SLTNode* pcur = *pphead;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);//链表不能为空assert(*pphead);//pos刚好是头结点if (pos == *pphead){//头插SLTPushFront(pphead, x);return;}//pos不是头结点的情况SLTNode* newnode = SLBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//找到了prev->next = newnode;newnode->next = pos;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除指定位之前的节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead);assert(pos);//pos刚好是头结点,没有前驱节点,执行头删if (*pphead == pos){//头删SLTPopFront(pphead);return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}//删除指定位置之后pos节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pos->next);//pos pos->next pos->next->nextSLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}//销毁链表
void SListDesTory(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
SList.h
#define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>
#include <stdlib.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** pphead, 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** pphead, SLTNode* pos);//销毁链表
void SListDesTory(SLTNode** pphead);
4. 链表的分类
链表的结构非常多样,一下情况组合组合起来就有8种链表结构:
链表说明:
在单链表中,“头结点”的“头”和“带头”链表是两个概念
单链表中提到的“头结点”指的是第一个有效的节点
“带头”链表里的“头”指的是无效的节点
虽然链表的种类非常之多,但是使用比较多的只有两种:单链表(不带头单向不循环链表)和双向链表(带头双向循环链表)
相关文章:

数据结构——单链表专题
目录 1. 链表的概念及结构2. 实现单链表初始化尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除指定位之前的节点删除指定位置之后pos节点销毁链表 3. 完整代码test.cSList.h 4. 链表的分类 1. 链表的概念及结构 在顺序表中存在一定的问题: …...

Linux:开源世界的王者
在科技世界中,Linux犹如一位低调的王者,统治着开源世界的半壁江山。对于许多技术爱好者、系统管理员和开发者来说,Linux不仅仅是一个操作系统,更是一种信仰、一种哲学。 一、开源的魅力 Linux的最大魅力在于其开源性质。与封闭的…...

⭐北邮复试刷题103. 二叉树的锯齿形层序遍历 (力扣每日一题)
103. 二叉树的锯齿形层序遍历 给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 示例 1:输入:…...

文件上传漏洞--Upload-labs--Pass07--点绕过
一、什么是点绕过 在Windows系统中,Windows特性会将文件后缀名后多余的点自动删除,在网页源码中,通常使用 deldot()函数 对点进行去除,若发现网页源代码中没有 deldot() 函数,则可能存在 点绕过漏洞。通过点绕过漏洞&…...

MySQL高级特性篇(1)-JSON数据类型的应用
MySQL是一种常用的关系型数据库管理系统,它提供了多种数据类型,其中包括JSON数据类型。JSON(JavaScript Object Notation)是一种常用的数据交换格式,它以键值对的形式组织数据,并支持嵌套和数组结构。MySQL…...

如何用Qt实现一个无标题栏、半透明、置顶(悬浮)的窗口
在Qt框架中,要实现一个无标题栏、半透明、置顶(悬浮)的窗口,需要一些特定的设置和技巧。废话不多说,下面我将以DrawClient软件为例,介绍一下实现这种效果的四个要点。 要点一:移除标题栏&#…...

ViT: transformer在图像领域的应用
文章目录 1. 概要2. 方法3. 实验3.1 Compare with SOTA3.2 PRE-TRAINING DATA REQUIREMENTS3.3 SCALING STUDY3.4 自监督学习 4. 总结参考 论文: An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 代码:https://github.com…...

Sora 的工作原理(及其意义)
原文:How Sora Works (And What It Means) 作者: DAN SHIPPER OpenAI 的新型文本到视频模型为电影制作开启了新篇章 DALL-E 提供的插图。 让我们先明确一点,我们不会急急忙忙慌乱。我们不会预测乌托邦或预言灾难。我们要保持冷静并... 你…...

Java学习笔记2024/2/16
知识点 面向对象 题目1(完成) 定义手机类,手机有品牌(brand),价格(price)和颜色(color)三个属性,有打电话call()和sendMessage()两个功能。 请定义出手机类,类中要有空参、有参构造方法,set/get方法。 …...

XLNet做文本分类
import torch from transformers import XLNetTokenizer, XLNetForSequenceClassification from torch.utils.data import DataLoader, TensorDataset # 示例文本数据 texts ["This is a positive example.", "This is a negative example.", "Anot…...

Swift 5.9 新 @Observable 对象在 SwiftUI 使用中的陷阱与解决
概览 在 Swift 5.9 中,苹果为我们带来了全新的可观察框架 Observation,它是观察者开发模式在 Swift 中的一个全新实现。 除了自身本领过硬以外,Observation 框架和 SwiftUI 搭配起来也能相得益彰,事倍功半。不过 Observable 对象…...

分享一个学英语的网站
名字叫:公益大米网 Freerice 这个网站是以做题的形式来记忆单词,题干是一个单词,给出4个选项,需要选出其中最接近题干单词的选项。 答对可以获得10粒大米,网站的创办者负责捐赠。如图 触发某些条件&a…...

【动态规划】【C++算法】2742. 给墙壁刷油漆
作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 LeetCode2742. 给墙壁刷油漆 给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time ,分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有…...

【后端高频面试题--设计模式上篇】
🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 往期精彩内容 【后端高频面试题–设计模式上篇】 【后端高频面试题–设计模式下篇】 【后端高频…...

P3141 [USACO16FEB] Fenced In P题解
题目 如果此题数据要小一点,那么我们可以用克鲁斯卡尔算法通过,但是这个数据太大了,空间会爆炸,时间也会爆炸。 我们发现,如果用 MST 做,那么很多边的边权都一样,我们可以整行整列地删除。 我…...

Android Compose 一个音视频APP——Magic Music Player
Magic Music APP Magic Music APP Magic Music APP概述效果预览-视频资源功能预览Library歌曲播放效果预览歌曲播放依赖注入设置播放源播放进度上一首&下一首UI响应 歌词歌词解析解析成行逐行解析 视频播放AndroidView引入Exoplayer自定义Exoplayer样式横竖屏切换 歌曲多任…...

Nginx实战:安装搭建
目录 前言 一、yum安装 二、编译安装 1.下载安装包 2.解压 3.生成makefile文件 4.编译 5.安装执行 6.执行命令软连接 7.Nginx命令 前言 nginx的安装有两种方式: 1、yum安装:安装快速,但是无法在安装的时候带上想要的第三方包 2、…...

Qt之条件变量QWaitCondition详解(从使用到原理分析全)
QWaitCondition内部实现结构图: 相关系列文章 C之Pimpl惯用法 目录 1.简介 2.示例 2.1.全局配置 2.2.生产者Producer 2.3.消费者Consumer 2.4.测试例子 3.原理分析 3.1.源码介绍 3.2.辅助函数CreateEvent 3.3.辅助函数WaitForSingleObject 3.4.QWaitCo…...

OpenSource - 一站式自动化运维及自动化部署平台
文章目录 orion-ops 是什么重构特性快速开始技术栈功能预览添砖加瓦License orion-ops 是什么 orion-ops 一站式自动化运维及自动化部署平台, 使用多环境的概念, 提供了机器管理、机器监控报警、Web终端、WebSftp、机器批量执行、机器批量上传、在线查看日志、定时调度任务、应…...

【后端高频面试题--设计模式下篇】
🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 后端高频面试题--设计模式下篇 往期精彩内容设计模式总览模板方法模式怎么理解模板方法模式模板方…...

这才是大学生该做的副业,别再痴迷于游戏了!
感谢大家一直以来的支持和关注,尤其是在我的上一个公众号被关闭后,仍然选择跟随我的老粉丝们,你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴,我决定分享一些实用的干货,这些都是我亲身实践并且取得成功的…...

Ubuntu20.04 安装jekyll
首先使根据官方文档安装:Jekyll on Ubuntu | Jekyll • Simple, blog-aware, static sites 如果没有报错,就不用再继续看下去了。 我这边在执行gem install jekyll bundler时报错,所以安装了rvm,安装rvm可以参考这篇文章Ubuntu …...

AWK语言
一. awk awk:报告生成器,格式化输出。 在 Linux/UNIX 系统中,awk 是一个功能强大的编辑工具,逐行读取输入文本,默认以空格或tab键作为分隔符作为分隔,并按模式或者条件执行编辑命令。而awk比较倾向于将一行…...

精通Nmap:网络扫描与安全的终极武器
一、引言 Nmap,即NetworkMapper,是一款开源的网络探测和安全审计工具。它能帮助您发现网络中的设备,并识别潜在的安全风险。在这个教程中,我们将一步步引导您如何有效地使用Nmap,让您的网络更加安全。 因为Nmap还有图…...

Java 学习和实践笔记(11)
三大神器: 官方网址: http://www.jetbrains.com/idea/ 官方网址: https://code.visualstudio.com/ 官方网址: http://www.eclipse.org 装好了idea社区版,并试运行以下代码,OK! //TIP To <b>Run</b> code, press &l…...

开发实体类
开发实体类之间先在pom文件中加入该依赖 <!-- 开发实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency>我们在实体类中声明各个属…...

人工智能学习与实训笔记(十五):Scikit-learn库的基础与使用
人工智能专栏文章汇总:人工智能学习专栏文章汇总-CSDN博客 本篇目录 一、介绍 1. 1 Scikit-learn的发展历程及定义 1.2 理解算法包、算法库及算法框架之间的区别和联系 二、Scikit-learn官网结构 三、安装与设置 3.1 Python环境的安装与配置 3.2 Scikit-lea…...

插值与拟合算法介绍
在数据处理和科学计算领域,插值与拟合是两种极为重要的数据分析方法。它们被广泛应用于信号处理、图像处理、机器学习、金融分析等多个领域,对于理解和预测数据趋势具有至关重要的作用。本文将深入浅出地介绍这两种算法的基本原理,并结合C语言编程环境探讨如何在CSDN开发者社…...

下一代Windows系统曝光:基于GPT-4V,Agent跨应用调度,代号UFO
下一代Windows操作系统提前曝光了?? 微软首个为Windows而设的智能体(Agent) 亮相: 基于GPT-4V,一句话就可以在多个应用中无缝切换,完成复杂任务。整个过程无需人为干预,其执行成功…...

二.自定义头文件
一.Worker.h 1.1概述 - 类名:Worker - 继承关系:所有其他类(Employee、Manager、Boss)都继承自该抽象类 - 头文件保护:使用 pragma once 防止头文件重复包含 - 引入标准库:包含 <iostream> 和 <st…...