数据结构——链表讲解(2)
作者:几冬雪来
时间:2023年3月5日
内容:数据结构链表讲解
目录
前言:
剩余的链表应用:
1.查找:
2.改写数据:
3.在pos之前插入数据:
4.pos位置删除:
5.在pos的后面插入:
6.pos后面进行删除:
7.代码:
结尾:
前言:
在上一篇博客中,我们初步认识了C语言,还完成了对链表初始化,头删头插和尾插尾删的代码书写和说明。并且对链表中代码书写过程可能会遇到的坑进行了讲解,最后还进行了链表与顺序表之间的对比。这让我们对链表了解了不少,但是链表的内容并没有结束,这篇博客我们将进一步的了解链表。
剩余的链表应用:
在上一篇博客中,我们讲解了链表的初始化,头插头删等四种应用方法,但是和顺序表一样,链表也不只这几种应用而已。那么今天我们将会将链表剩下的程序都讲完。
1.查找:
首先就是在链表中查找一个数据,链表查找数据和顺序表查找数据的方法是异曲同工的,也就是暴力查找,这里就不进行过多的说明,直接上代码。
这个就是我们查找值的代码,先把phead的值赋给我们创立的cur,接下来写一个循环来判断,再后来在循环中嵌套一个判断语句。如果cur->data为我们要寻找的值,那么就可以直接返回cur的地址,第一次没有找到的话就让cur->next并赋值给cur。相反,如果整个链表都遍历完了还是没有找到那个值,这里就直接返回空。
2.改写数据:
在书写完了查找数据的代码,接下来就是增删查改中的改,也就是改写数据。但是这里改写数据并不用写一个全新的代码,我们可以沿用上面查找数据的代码,并对其略微进行添加和修改。
这里就需要我们先在链表中找到那个值后,对其进行修改,这就是修改链表数据的方法。
3.在pos之前插入数据:
讲解完了两个较清楚的代码之后,我们来讲在pos之前插入数据的方法。
既然了解了原理,那么下来就开始着手代码的书写了。
首先代码一进去就需要对其断言,如果pos传了一个我们没有的值,那么代码下面的prev->next的循环条件就一直不满足,最后会访问到空指针。
如果我们的pos的地址和pphead的地址一致的话,那么这里就相当于头插操作。要是不一致的话,我们先创建一个指针指向第一个结点的位置。而后进行判断,prev->next不为pos的时候,prev进行修改赋值,这里找到的为pos的前一个值。
剩下的就更加简单了,找到pos前一个值后,我们想创立一个新结点,并命名为newnode。
然后让我们原先上一个结点的next指向这个新结点的地址。有因为创建结点的时候,newnode->next为0,因此这里要将pos给newnode->next,使它指向pos。
这里就将我们4之前(第一个4)插入一个值为20的结点。
那么在我们插入代码的过程中,只需要对pos进行断言吗?其实不止,在这里不仅仅要对pos进行断言,而且还要对pphead进行断言。
这是为什么呢?还记得我们的**pphead是指向指针plist的地址,那么如果plist为空的话,pphead会为空吗?这里是一定不会的,为什么?我们画一张图来了解一下。
这里plist为指针,如果plist为空的话。但是在这里的**pphead为指针,存放的是plist这个值的地址,即使plist为空但是它还是有地址的存在,地址不为空。
在数据结构中,断言的操作并没有什么规律可循,我们只要知道当我们一个值一定不能为空的时候要进行断言。
既然知道了这个道理,那我们上面的头删尾删等涉及到pphead的地方都应该进行断言,为的是防止我们传错。
4.pos位置删除:
下一个就是我们pos位置的删除,有了上面的基础,一开始我们就要对其进行断言。
这里如果指针pos等于pphead,pphead指向第一个结点的地址,那么这个时候这里就相当于我们的头删操作了。
如果这里的pos不为头结点的地址,那么就进入另一个分支语句。将头结点的位置赋值给新创立的一个指针prev,如果prev->next不为我们要的值,那就对prev进行修改,找到以后将我们pos->next也就是pos下一个结点的地址给pos上一个结点的next,最后将pos进行释放即可。
这里将pos释放之后,我们可以不用将它置为空。因为我们的pos是语句局部变量,形参的改变不影响实参。
在这里我们pos置空的操作在这里进行即可。
通过书写上面代码我们发现,单链表其实不太适合进行前面插入和删除当前位置,我们的单链表更适合后面插入或者删除后面位置。
5.在pos的后面插入:
在这里我们知道在单链表中不太适合进行前面插入和删除当前位置,我们通常运用的是后面插入和删除后面位置的值。那么现在就先来讲解——在pos的后面插入的代码是怎么样写的。
首先还是我们的断言,并且创建一个新的结点来进行插入操作。
那么我们的代码是怎么样进行的呢?
这里就是单链表pos后面插入一个值的方法,但是有一部分人跟着这个图写代码却掉进坑里,这又是怎么回事? 我们将它们的代码写出:
这个代码大家看得出来哪里出错了吗?我们来看一看,在创建新结点后,newnode的值赋值给pos->next,这里的next就是指向我们newnode的值,而后再把原pos->next,也就是我们插入后newnode后面的一个结点,我们将它的地址交给newnode。
看似一切都没有问题,逻辑上说得通,且看样子貌似可以运行,但是实际上这个代码是错误的。
在这个代码中,我们pos->next指向我们的新结点这一步是没有错误的,那么报错的就是我们接下来的一步了,这里代码的pos的next原本指向的是未修改前下一个结点的地址,但是在上一步我们在上一步就另我们的pos->next指向我们的新结点,因此这里的pos->next的赋值已经不是原来的值。
那我们的代码要怎么修改,其实很简单。
我们只需要将两个代码的书写顺序调换一下即可,这里也提醒我们写代码要注意它的执行顺序。
6.pos后面进行删除:
接下来就是我们的pos后面进行删除,删除的代码相较于插入的代码可能还要更简单一点。
在这里我们仅需要注意要将我们删除的值先进行一次保留,如果直接pos->next=pos->next->next的话,中间那个元素就会被省略掉。
7.代码:
到这里我们的链表内容基本结束了,在最后我将所有的代码写上。
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;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 SLTErase(SLTNode** pphead, SLTNode* pos);//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//pos位置后面删除
void SLTEraseAfter(SLTNode* pos);
test.c文件
#include "SLish.h"//void TestSList1()
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
//
// SLTPrint(plist);
//}//void TestSList2()
//{
// SLTNode* plist = NULL;
// SLTPushFront(&plist, 1);
// SLTPushFront(&plist, 2);
// SLTPushFront(&plist, 3);
// SLTPushFront(&plist, 4);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//}//void TestSList3()
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
//
// SLTPrint(plist);
//
// SLTPopFront(&plist);
// SLTPrint(plist);
//
// SLTPopFront(&plist);
// SLTPrint(plist);
//
// SLTPopFront(&plist);
// SLTPrint(plist);
//
// SLTPopFront(&plist);
// SLTPrint(plist);
//}//void TestSList4()
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
//
// SLTPrint(plist);
//
// SLTNode* ret = SLTFind(plist, 2);
// ret->data *= 2;
// SLTPrint(plist);
//
// SLTInsert(&plist, ret, 20);
// SLTPrint(plist);
//}void TestSList5()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 2);SLTPrint(plist);SLTErase(&plist, ret);ret = NULL;SLTPrint(plist);
}int main()
{/*TestSList1();*//*TestSList2();*//*TestSList3();*///TestSList4();TestSList5();return 0;
}
SList.c文件
#include "SLish.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL)//while(cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead,SLTDataType x)
{assert(pphead);/*SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;*/SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);/*assert(*pphead);*/if (*pphead == NULL){return;}if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}//while(tail->next->next!=NULL)//{// tail = tail->next;//}//free(tail->next);//tail->next = NULL;free(tail);tail = NULL;prev->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);/*assert(*pphead);*/if (*pphead == NULL){return;}SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);assert(pphead);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}//pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pphead);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//SLTNode* del = pos->next;//pos->next = pos->next->next;SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
结尾:
这篇博客的结束也意味着我们在链表方面的知识学习结束了,但是进入了数据结构的学习中,每次学习不再是一次就能吃透,代码的难度都得到了提升,在这种情况下我们更应该反复学习巩固知识。随着难度的提高,我的表达能力一定程度被限制了,这可能会使博客质量会下滑,这一点望理解,最后还是希望这篇对在学习链表的人有所帮助。
相关文章:

数据结构——链表讲解(2)
作者:几冬雪来 时间:2023年3月5日 内容:数据结构链表讲解 目录 前言: 剩余的链表应用: 1.查找: 2.改写数据: 3.在pos之前插入数据: 4.pos位置删除: 5.在pos的后…...

Elasticsearch:图片相似度搜索的 5 个技术组成部分
作者:Radovan Ondas,Bernhard Suhm 在本系列博文的第一部分中,我们介绍了图像相似度搜索,并回顾了一种可以降低复杂性并便于实施的高级架构。 此博客解释了实现图像相似性搜索应用程序所需的每个组件的基本概念和技术注意事项。 学…...

【CVPR2022】Class Re-Activation Maps for Weakly-Supervised Semantic Segmentation
论文标题:Class Re-Activation Maps for Weakly-Supervised Semantic Segmentation收录:CVPR 2022paper: https://arxiv.org/abs/2203.00962code: https://github.com/zhaozhengChen/ReCAM解读:https://zhuanlan.zhihu.com/p/478133151https:…...

PMP项目管理项目运行环境
目录1 概述2 事业环境因素和组织过程资产3 组织系统3.1 概述3.2 组织治理框架3.2.1 治理框架3.2.2 项目治理3.3 管理要素3.4 组织结构类型3.4.1 组织结构类型3.4.2 项目管理办公室1 概述 项目所处的环境可能对项目的开展产生有利或不利的影响,这些影响的两大主要来…...

Vue 3.0 渲染函数 【Vue3 从零开始】
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。 让我们深入一个简单的例子,这个例子里 render 函数很实用。假设我们要生成一些…...

西电软件体系结构核心考点汇总(期末真题+核心考点)
文章目录前言一、历年真题二、核心考点汇总2.1 什么是软件体系架构?(软件体系结构的定义)2.2 架构风格优缺点2.3 质量属性2.4 质量评估前言 主要针对西安电子科技大学《软件体系结构》的核心考点进行汇总。 【期末期间总结资料如下】 针对西电计科院软件工程专业大三《软件体…...

SRS源码分析-SDP内容解析
前言 在学习SRS的RTC模块之前,首先来分析下SRS在将rtmp推流转成rtc流,通过浏览器拉取webrtc流场景下产生的SDP内容 SDP格式介绍 SDP数据是文本格式,由多个 <key><value> 表达式构成,<key>的值只能是一个字符…...

HTML 颜色
HTML 颜色 HTML 颜色采用的是 RGB 颜色,是通过对红 (R)、绿 (G)、蓝 (B) 三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB 即是代表红、绿、蓝三个通道的颜色。 Color Values HTML 颜色由一个十六进制符号来定义,这个符…...

MySQL高可用架构之InnoDB Cluster部署
MySQL高可用架构之InnoDB Cluster部署InnoDB Cluster适用场景准备工作安装MySQL Shell使用MySQL Shell搭建InnoDB Cluster初始化第一个实例创建InnoDB Cluster添加副本实例创建相关用户MySQL Router部署安装MySQL Router引导MySQL Router启动MySQL Router环境准备 主机名IPOS版…...

Linux安装minio单机版
说明:因为前面记录了一下minio的使用,这里说一下minio的安装,只是单机版哦 环境准备:Linux系统 说明图: 1.创建文件夹命令 我的是安装在/usr/local 文件夹下面的创建文件夹命令 #进入目标文件夹 cd /usr/local#创建…...

网络总结知识点(网络工程师必备)四
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放,树高千尺,落叶归根人生不易,人间真情 目录 前言 71.NAPT有什么特点? 72.ARP欺骗解决方法...

数据结构——第三章 栈与队列(5)
共用栈和双队列1.共用栈2.双端队列栈与队列的本章小节1.共用栈 在实际应用中,有时一个应用程序需要多个栈,但这些栈的数据元素类型相同。假设每个栈都采用顺序栈,由于每个栈的使用情况不尽相同,势必会造成存储空间的浪费。若让多…...

CSDN竞赛第33期题解
CSDN竞赛第33期题解 1、题目名称:奇偶排序 给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。(奇数和偶数的顺序根据输入的数字顺序排 列) #include<bits/stdc.h> using namespace std; t…...

农产品销售系统的设计与实现
技术:Java、JSP等摘要:这篇文章主要描述的是农产品蔬菜在线销售系统的设计与实现。主要应用关于JSP网站开发技术,并联系到网站所处理的数据的结构特点和所学到的知识,应用的主要是Mysql数据库系统。系统实现了网站的基本功能&…...

C语言-基础了解-08-C判断
C判断 一、C判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。 C 语言把任何非零和非空的值假定为 true,把零或 null 假…...

用数组名作函数参数的详解,以及形参实参采用数组名,形参实参采用指针变量的几种情况解析
关于地址,指针,指针变量可以参考我的这篇文章: 地址,指针,指针变量是什么?他们的区别?符号(*)在不同位置的解释?_juechen333的博客-CSDN博客https://blog.csd…...

k8s中的PV和PVS
前言:容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)…...

【云原生】Gateway网关选型
网关一般分为流量网关和业务网关,流量网关负责接入所有的流量,并分发给不同的子系统,那在具体的业务接入之前,还有一层业务网关。流量网关提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局…...

QML Button详解
1.Button简介 Button表示用户可以按下或单击的按钮控件。按钮通常用于执行一个动作,或回答一个问题。典型的按钮有确定、应用、取消、关闭、是、否和帮助。 Button继承自AbstractButton,提供了以下几种信号。 void canceled() //当按…...

【编程实践】什么是好/坏代码?非程序员的示例
What is good/bad code? An illustrated example for non-programmers 什么是好/坏代码?非程序员的示例 目录 What is good/bad code? An illustrated example for non-programmers什么是好/坏代码?非程序员的示例 So what is ‘Bad Code’, as a layperson?那么,作为…...

一个简单的Sublime设置
问题 如果读者熟悉我,应该会发现我经常使用 VSCode 作为主力编辑器,但随着我安装的 VSCode 的插件逐渐增加,我发现对于部分较小的任务使用 VSCode 过于笨重,比如简单的 Markdown 文件编辑工作。 在经过一系列寻找后,…...

c语言经典例题-选择结构程序设计进阶
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 快递费用计算: 题目: 代码思路: 代码表示: 计算一元二…...

NOI 2023春季测试 游记
怎么说,没遇到大波折即幸运。 Day 0 睡到下午三点,然后列了一堆复习计划,大概是左偏树等一些早就忘没的科技。 但是沉迷打块,最后基本什么计划也没完成。 白天睡多了,晚上睡不着,大概半梦半醒过了一整夜…...

【VC 7/8】vCenter Server 基于文件的备份和还原Ⅱ——使用 FTP 协议备份 VC(VAMI 英文)
目录2. 备份 vCenter Server2.1 使用 FTP 协议备份 VC(1)登录 vCenter Server 管理界面(2)进入Backup页面(3)配置 Backup Schedule(4)开始备份(5)备份成功&am…...

Python基础—文件操作(二)
Python基础—文件操作(二) CSV格式文件 逗号分隔值,以纯文本形式存储表格数据 由任意数目的记录组成,记录间以换行符分隔 每条记录由字段组成,字段间用逗号或制表符分隔 每条记录都有同样的字段序列 如有列名,位于文件第一行 每条…...

学校的班级个数【并查集基础应用,Java实现】
题目描述 现有一个学校,学校中有若干个班级,每个班级中有若干个学生,每个学生只会存在于一个班级中。如果学生A和学生B处于一个班级,学生B和学生C处于一个班级,那么我们称学生A和学生C也处于一个班级。 现已知学校中共…...

WSL2使用Nvidia-Docker实现CUDA版本自由切换
众所周知,深度学习的环境往往非常麻烦,经常不同的项目所依赖的 torch、tensorflow 包对 CUDA 的版本也有不同的要求,Linux 下进行 CUDA 的管理比较麻烦,是一个比较头疼的问题。 随着 WSL2 对物理机显卡的支持,Nvidia-…...

pygame9 扫雷游戏2
一、响应鼠标左键事件 pygame.MOUSEBUTTONDOWN 表示鼠标事件发生, pygame.mouse.get_pressed()[0] 确认是鼠标左键被按下 pygame.mouse.get_pos() 获取到鼠标按下时的坐标值。 因此,我们可以在事件逻辑中例用此三个函数判断鼠标事件及对应的坐标&#x…...

逻辑电路代数运算(上)
逻辑代数L是一个封闭的代数系统,由一个逻辑变量集K,常量0和1,以及与或非三种基本运算构成。 参与逻辑运算的变量叫逻辑变量,用字母A,B……表示。每个变量的取值非0 即1。 0、1不表示数的大小,而是代表两种不…...

Rabbit快速入门
入门案例 需求:使用简单模式完成消息传递 步骤: 创建工程(生成者、消费者) 分别添加依赖 编写生产者发送消息 编写消费者接收消息 3.1.2. 添加依赖 往heima-rabbitmq的pom.xml文件中添加如下依赖: <dependenc…...