数据结构——链表讲解(2)

news2024/12/29 10:08:48

作者:几冬雪来

时间: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;
}

 结尾:

这篇博客的结束也意味着我们在链表方面的知识学习结束了,但是进入了数据结构的学习中,每次学习不再是一次就能吃透,代码的难度都得到了提升,在这种情况下我们更应该反复学习巩固知识。随着难度的提高,我的表达能力一定程度被限制了,这可能会使博客质量会下滑,这一点望理解,最后还是希望这篇对在学习链表的人有所帮助。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零死角玩转stm32初级篇1-STM32如何编译和下载程序

本篇博文目录:一.程序的编译二.程序的下载1.ISP方式2.JTAG方式3.SWD方式4.SWIM方式一.程序的编译 Keil uVision5 工具中有四个编译如图&#xff0c;他们分别表示什么意思,下面进行介绍,解释来源于<<零死角玩转stm32>>。 第一个按钮&#xff1a; Translate 就是翻译…

【项目实战】Linux下安装Nginx教程

一、环境准备 Linux版本&#xff1a;CentOS7 64位 二、具体步骤 2.1 步骤1&#xff1a;确认系统中安装以下基础依赖 确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel。 在安装Nginx前首先要确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel。 yu…

Feature interation—— Bridge、Fusion、Filte

Feature interation&#xff08;特征交互&#xff09;&#xff1a;物品不同模态的表示属于不同的语义空间&#xff0c;并且每个用户对模态也有不同的偏好。因此&#xff0c;多模态推荐系统&#xff08;MRS&#xff09;寻求融合和交互多模态特征来生成用户和物品的特征表示。特征…

STM32开发(六)STM32F103 通信 —— RS485 Modbus通信编程详解

文章目录一、基础知识点二、开发环境三、STM32CubeMX相关配置1、STM32CubeMX基本配置2、STM32CubeMX RS485 相关配置四、Vscode代码讲解五、结果演示以及报文解析一、基础知识点 了解 RS485 Modbus协议技术 。本实验是基于STM32F103开发 实现 通过RS-485实现modbus协议。 准备…

DJ1-1 计算机网络和因特网

目录 一、计算机网络 二、Interent 1. Internet 的介绍 2. Internet 的具体构成 3. Internet 提供的服务 4. Internet 的通信控制 一、计算机网络 定义&#xff1a;是指两台以上具有独立操作系统的计算机通过某些介质连接成的相互共享软硬件资源的集合体。 计算机网络向…

Python机器学习17——极限学习机(ELM)

本系列基本不讲数学原理&#xff0c;只从代码角度去让读者们利用最简洁的Python代码实现机器学习方法。 背景&#xff1a; 极限学习机(ELM)也是学术界常用的一种机器学习算法&#xff0c;严格来说它应该属于神经网络&#xff0c;应该属于深度学习栏目&#xff0c;但是我这里把它…

C/C++开发,无可避免的多线程(篇四).线程与函数的奇妙碰撞

一、函数、函数指针及函数对象 1.1 函数 函数&#xff08;function&#xff09;是把一个语句序列&#xff08;函数体, function body&#xff09;关联到一个名字和零或更多个函数形参&#xff08;function parameter&#xff09;的列表的 C 实体&#xff0c;可以通过返回或者抛…

MongoDB分片教程

一、概述分片是一种将数据分布在多个 机器。MongoDB使用分片来支持具有非常大数据的部署 集和高吞吐量操作。具有大型数据集或高吞吐量应用程序的数据库系统可以 挑战单个服务器的容量。例如&#xff0c;高查询率可以 耗尽服务器的 CPU 容量。工作集大小大于 系统的 RAM 会给磁…

初学者的第一个Linux驱动

软件环境&#xff1a;Ubuntu20.04 Linux内核源码&#xff1a;3.4.39 硬件环境&#xff1a;GEC6818 什么是驱动&#xff1f;简单来说就是让硬件工作起来的程序代码。 Linux驱动模块加载有两种方式&#xff1a; 1、把写好的驱动代码直接编译进内核。 2、把写好的驱动代码编…

Linux24 -- tcp相关概念、多个客户端链接服务端代码

一、tcp相关概念 tcp协议特点&#xff1a;面向连接的、可靠的、流式服务 建立链接&#xff1a;三次握手&#xff0c;发送 SYN 断开链接&#xff1b;四次挥手&#xff0c;发送 FIN tcp、udp都同属于传输层&#xff0c;在网络层使用ip协议&#xff0c;都要将数据交给IP协议&am…

零拷贝技术-内核源码剖析

在网络编程中&#xff0c;如果我们想要提供文件传输的功能&#xff0c;最简单的方法就是用read将数据从磁盘上的文件中读取出来&#xff0c;再将其用write写入到socket中&#xff0c;通过网络协议发送给客户端。ssize_t read(int fd, void *buf, size_t count); ssize_t write(…

学习记录---latent code 潜在编码

文章目录参考文献1. 什么是潜在编码&#xff1f;2.什么是潜在空间&#xff1f;3.同类潜在编码的相似性4.潜在编码的应用4.1 Antoencoders4.2 Generative models5.结论个人学习总结&#xff0c;持续更新中……参考文献 [1] 快速理解深度学习中的latent code潜在编码 1. 什么是…

[一篇读懂]C语言十一讲:单链表的删除和单链表真题实战

[一篇读懂]C语言十一讲&#xff1a;单链表的删除和单链表真题实战1. 与408关联解析及本节内容介绍1 本节内容介绍2. 单链表的删除操作实战3. 单链表真题解读与解题设计1 题目解读2 解题设计第一阶段&#xff1a;双指针找中间结点第二阶段&#xff1a;原地逆置第三阶段&#xff…

ubuntu16.04 python代码自启动和可执行文件自启动

1 python代码自启动 参考 https://blog.csdn.net/qq_38288618/article/details/104096606 准备好python文件 test.py import time c1 while 1:time.sleep(1)cc1print(c)运行 sudo chmod 777 test.py python3 test.py准备run.sh 文件 #!/bin/bash gnome-terminal -x bash -…

【Spring6】IoC容器之基于XML管理Bean

3、容器&#xff1a;IoC IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容…

C语言学习笔记——指针(初阶)

前言 指针可以说是C语言基础语法中最难的理解的知识之一&#xff0c;很多新手&#xff08;包括我&#xff09;刚接触指针时都觉得很难。在我之前发布的笔记中都穿插运用了指针&#xff0c;但是我一直没有专门出一期指针的笔记&#xff0c;这是因为我确实还有些细节至今还不太清…

STM32之关门狗

看门狗介绍在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会造成整个系统的陷入…

vue3+rust个人博客建站日记5-所有界面

没有数据的前端&#xff0c;是没有灵魂的。明明标题是vue3 rust &#xff0c;但日记撰写至今&#xff0c;似乎只有第一篇提及了Rust&#xff0c;这可不行。是时候一股作气&#xff0c;完成大部分页面绘制工作了&#xff01; 最后再说一次&#xff0c;时间要加速了。 ——普奇神…

EPICS S7nodave手册

第一章&#xff1a;介绍 本手册分为6章(不算次介绍部分)。第一章介绍s7nodave用于EPICS的设备支持的概念和特新。第二章描述启动一个使用s7nodave的IOC项目所需要的几步。第三章描述s7nodave支持的IOC shell命令。之后&#xff0c;第四章解释s7nodave支持的各种记录类型。最后…

【算法】期末复盘,酒店住宿问题——勿向思想僵化前进

文章目录前言题目描述卡在哪里代码&#xff08;C&#xff09;前言 省流&#xff1a;一个人也可以住双人间&#xff0c;如果便宜的话。 害&#xff01;尚正值青春年华&#xff0c;黄金岁月&#xff0c;小脑瓜子就已经不灵光咯。好在我在考试的最后一分钟还是成功通过了这题&am…