单链表(增删改查)【超详细】

news2024/11/27 20:39:36

目录

单链表

1.单链表的存储定义

2.结点的创建

3.链表尾插入结点

4.单链表尾删结点

 5.单链表头插入结点 

6.单链表头删结点

7.查找元素,返回结点

 8.在pos结点前插入一个结点

​编辑

 9.在pos结点后插入一个结点

10.删除结点

11.删除pos后面的结点

12.修改链表结点的值

13.打印链表

 14.销毁链表


线性表的链式存储:链表

前言:

之前介绍过线性表的顺序存储方式:顺序表  发现顺序表在 插入删除操作需要移动大量元素;当静态顺序表长度变化较大时,难以确定存储空间的容量;造成存储空间的碎片。

数据结构中: 

注意 这里的是没有哨兵卫的链表。 

单链表

1.单链表的存储定义

单链表的存储与顺序表的存储不一样,单链表不仅仅存放数值,还要存放下一个结点的地址

代码 

typedef int SLNDataType;

typedef struct SListNode 
{
	SLNDataType val;	//存放单链表的值
	struct SListNode* next; //存放下一个单链表的地址
}SLNode;

当有了单链表的存储定义,我们就可以对单链表进行存放结点。 

2.结点的创建

这里使用malloc函数进行创建

代码

//创建一个结点
SLNode* CreateNode(SLNDataType x) 
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("CreateNode->malloc");
		exit(-1);
	}
	newnode->val = x;  //结点赋值
	newnode->next = NULL; //结点下一个结点为NULL
	return newnode;
}

3.链表尾插入结点

尾插结点:这里需要考虑两方面。

1、单链表为空,插入结点的情况,这里直接让头指针指向创建的新结点即可。

2、单链表不为空时,例如下图要尾插结点4

这里只需创建一个尾指针tail 遍历到第三个结点,把 第三个结点的next 指向 第四个结点即可

代码

//尾插结点
void SListPushBack(SLNode** pphead, SLNDataType x) 
{
    assert(pphead);
	SLNode* newnode = CreateNode(x);    //创建一个结点
	if (*pphead == NULL)	//单链表为空直接指向新结点
	{
		*pphead = newnode;
	}
	else  
	{
		SLNode* tail = *pphead;	//定义一个尾指针
		while (tail->next != NULL)	//找到表尾
		{
			tail = tail->next;	//指向下一个结点
		}
		//找到表尾后指向新结点即可
		tail->next = newnode;
	}
}

注意这里的二级指针。*pphead == phead,指向第一个结点。 

4.单链表尾删结点

单链表尾部删除结点:分两种情况。1)只有一个结点的情况   2)多个结的情况

因为平时删除结点,对于单链表,我们需要找到前面的结点。所以这里采取经典的双指针的方法

定义一个指向当前的结点(cur),一个指向前面的结点(pre),当遍历到表尾时,释放cur指向的结点后,把pre->next = NULL,这样就完成了尾删一个结点。

但是我们发现只有一个结点时

这里free(cur) ,但是pre指向NULL, 这条语句 pre->next = NULL 就是错的。

当然有些人会可能想到,为什么不让pre也指向第一个结点,如果这样想,这里显然是对free()这知识点模糊不清 。因为free(cur) 时 第一个结点的内存空间已经释放返还给操作系统了,所以pre此时指向的内存空间,属于访问野指针了。

所以我们对 1)只有一个结点的情况   2)多个结点的情况 分别处理

1)一个结点的情况

	if ((*pphead)->next == NULL)  //只有一个结点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}

2) 多个结点的情况

定义一个指向当前的结点(cur),一个指向前面的结点(pre),当遍历到表尾时,释放cur指向的结点后,把pre->next = NULL,这样就完成了尾删一个结点。

		SLNode* cur = *pphead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur = cur->next;
		}

		free(cur);
		cur = NULL;
		pre->next = NULL;

 代码

//尾删结点
void SListPopBack(SLNode** pphead) 
{
    assert(pphead);
	assert(*pphead); //避免链表为空
	if ((*pphead)->next == NULL)  //只有一个结点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else  //多个结点的情况
	{
		SLNode* cur = *pphead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur = cur->next;
		}
		free(cur);
		cur = NULL;
		pre->next = NULL;
	}
}

当然上方代码也可以不用定义pre,来实现

//尾删结点
void SListPopBack(SLNode** pphead) 
{
	assert(*pphead); //避免链表为空
	if ((*pphead)->next == NULL)  //只有一个结点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else  //多个结点的情况
	{
		SLNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

 5.单链表头插入结点 

首先创建一个新结点,然后让新结点指向 头指针指向的结点,头指针再指向新结点。

注意先后指向的顺序不能变。

代码

//头插结点
void SListPushFront(SLNode** pphead, SLNDataType x) 
{
    assert(pphead);
	SLNode* newnode = CreateNode(x);	//创建一个新结点
	newnode->next = *pphead;	//新结点指向 头指针指向的结点
	*pphead = newnode;	//再让头指针指向新结点
}

这样就变成

 如果指向的顺序变了,那就会出现这种错误

结点的next指向自己,这样是错误的

6.单链表头删结点

单链表头删除结点时,先临时创建一个next指针来指向第一个结点的下一个结点,然后释放第一个结点,再让头指针指向next,这样就完成删除第一个结点。

代码

//头删结点
void SListPopFront(SLNode** pphead) 
{
    assert(pphead);
	assert(*pphead);
	SLNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

方法二

当然也可以先临时存放第一个结点,让*pphead指向第二个结点,然后再删除第一个结点。

代码

//头删结点
void SListPopFront(SLNode** pphead) 
{
    assert(pphead);
	assert(*pphead);
    SLNode* tmp = *pphead;
    *pphead = (*pphead)->next;
	free(tmp);
}

7.查找元素,返回结点

代码

//查找元素,返回结点
SLNode* SListFind(SLNode* phead, SLNDataType x) 
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x)
			return cur;	//查找成功返回当前结点
		cur = cur->next;
	}
	return NULL; //查找失败返回NULL
}

 8.在pos结点前插入一个结点

assert(phead&&pos); 这里的意思是避免是空指针的情况

 首先,在某结点前插入结点,当在第一个结点前插入结点时,这相当于表头插入结点;当在不是第一个结点前插入结点时,定义一个pre的指针遍历找到pos结点的前一个结点。

代码

//在pos结点前插入一个结点
void SListInsert(SLNode** pphead, SLNode* pos, SLNDataType x) 
{
    //避免指针为空
	assert(pphead);
    assert(*pphead); 
    assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead,x);
		return;
	}
	SLNode* newnode = CreateNode(x);
	SLNode* pre = *pphead;  //定义一个指向第一个结点的指针
	while (pre->next != pos)
	{
		assert(pre);//避免空指针
		pre = pre->next;
	}
	pre->next = newnode;
	newnode->next = pos;
}

图解

注意更改顺序 

上述说的是在有头指针的情况下进行在pos结点前插入一个结点。

题目变形 

当在没有给头指针的情况下,在pos的结点前如何插入一个结点?

解析:这里给出一个取巧的方法,即,先在pos后面插入结点,然后再把pos的值和新插入结点的值交换一下。这样就完成了在没有头指针的情况下在pos前插入一个结点。

代码

//在pos结点前插入一个结点
void SListInsert(SLNode* pos, SLNDataType x) 
{
    //避免指针为空
    assert(pos);
    SLNode* newnode = CreateNode(x);
    //先在pos后插入结点
    newnode->next = pos->next;
    pos->next = newnode;
    //交换两个结点的值
    SLNDataType tmp = pos->val;
    pos->val = newnode->val;
    newnode->val = tmp;
}

图解

 9.在pos结点后插入一个结点

这里定义一个指针next用于记录pos后面结点的地址,pos指向newnode, newnode指向next。

 代码

//在pos结点后插入结点
void SListInsertAfter(SLNode* pos, SLNDataType x) 
{
	assert(pos);
	SLNode* next = pos->next;	//先记录pos后面的结点
	SLNode* newnode = CreateNode(x);
	pos->next = newnode;
	newnode->next = next;
}

 

也可以这样写,注意顺序,避免指向自己。

  代码

//在pos结点后插入结点
void SListInsertAfter(SLNode* pos, SLNDataType x) 
{
	assert(pos);
	SLNode* newnode = CreateNode(x);
	newnode->next = pos->next;
    pos->next = newnode;
}

10.删除结点

例:删除结点pos, 在只有一个结点中删除结点,在多个结点中删除结点。

在只有一个结点中删除,那就相当于头删结点。

在多个结点中删除结点,那就需要知道被删除结点的前一个结点。这里采用双指针的思想进行删除结点。pre指针负责记录pos的前一个结点,next指针记录pos后面的结点。这样释放pos结点后,pre指向next,就完成了pos结点的删除

 代码

//删除结点pos
void SListErase(SLNode** pphead, SLNode* pos) 
{
	assert(pphead);
    assert(pos);
	assert(*pphead);
	//pos为第一个结点且是第一个结点
	if (*pphead == pos)
	{
		//相当于头删结点
		SListPopFront(pphead);
	}
    else
    {
       	//在多个结点中删除结点pos
	    SLNode* pre = *pphead;
	    SLNode* next = pos->next;
	    while (pre->next != pos)
	    {
		    pre = pre->next;
	    }
	    free(pos);
	    pos = NULL;
	    pre->next = next; 
    }

}

图解 

11.删除pos后面的结点

首先断言一下,避免头指针和pos后面的结点为空。(assert(phead && pos->next);)

 代码

//删除pos之后的一个结点
void SListEraseAfter(SLNode* pos) 
{
    assert(pos);
	assert(pos->next); //避免为空指针
	//先使用next记录pos后面的结点
	SLNode* next = pos->next->next;
	free(pos->next);
	pos->next = next;
}

图解

12.修改链表结点的值

  代码

//修个pos结点中的值
void SListModify(SLNode* phead, SLNode* pos, SLNDataType x)
{
	assert(phead&&pos); //避免为空指针
	SLNode* cur = phead; //cur指向头指针
	while(cur != pos)
	{
		cur = cur->next;
	}
	cur->val = x; //修个值
}

13.打印链表

  代码

//打印结点
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;
	while (cur)
	{
		printf("%d->",cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

 14.销毁链表

这里因为是使用mallco开辟的空间,所以是需要进行手动释放的。

注意:可能是多个结点,也就是开辟了多个不连续内存空间

所以,首先应该记录被释放结点的下一个结点的地址,避免释放后找不到下一个结点

释放完所有的结点,头指针置为NULL

 代码

//销毁链表
void SListDestory(SLNode** pphead) 
{
	assert(pphead);
	SLNode* p = *pphead;
	//注意结点的内存空间的释放,要释放多个
	while (p) 
	{
        //首先应该记录被释放结点的下一个结点的地址,避免释放后找不到下一个结点
		SLNode* tmp = p->next; 
		free(p);
		p = tmp; //指向下一个结点
	}
	//释放完所有的结点,头指针置为NULL
	*pphead = NULL;
}

总结:

单链表在找出位置的指针后,插入和删除时间复杂度为O(1),

但在查找方面上,单链表的时间复杂度为O(n),

当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构。

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

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

相关文章

XOR Construction

思路: 通过题目可以得出结论 b1^b2a1 b2^b3a2 ....... bn-1^bnan-1 所以就可以得出 (b1^b2)^(b2^b3)a1^a2 b1^b3a1^a2 有因为当确定一个数的时候就可以通过异或得到其他所有的数,且题目所求的是一个n-1的全排列 那么求出a的前缀异或和arr之后…

微软surface laptop禁用触摸屏(win10、设备管理器)

参考链接: 在屏幕中启用和禁用触摸屏Windows 设置如下

asp.net校园招聘管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 校园招聘管理系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言开发 应用技术:asp.net c#s…

黄执中老师人际说服课思考总结(个人笔记整理 ②)

前言: 沟通和说服的区别:为什么沟通不能解决问题,处于劣势的一方(承受问题的那方)才想去沟通(对方没有沟通动力)。说服是温柔而有力的学科 - 劣势一方的武器。 说服是一门影响人的学问&#xff…

谷歌浏览器安装 vue-devtools 拓展,仅需3分钟,提供插件

1、vue-devtools 扩展 存储在百度网盘地址: 链接:https://pan.baidu.com/s/1LDIJxG26tOHtUe_aUh_pEA 提取码:v81r 下载下来后是一个.crx 文件 2、浏览器打开扩展程序 1、可以通过地址访问 chrome://extensions/ 2、可以自行找到相应位置…

探索双十一:从技术角度剖析电商狂欢节

每年的11月11日,全球最大的在线购物狂欢节“双十一”在中国掀起了一场规模空前的消费风暴。以阿里巴巴为代表的电商平台和众多品牌商家,不仅为消费者提供了数以亿计的优惠商品,同时也将这一活动打造成了一个科技与商业完美结合的标志事件。本…

ogg怎么转mp3格式?三个方法值得一试!

Ogg格式的缺点很多,比如其播放兼容性差、普及性差、无优势、对多声道系统的限制、专业音频制作软件不支持,但是保不齐我们有时候就有一个音频是ogg格式,那么如何把他转换成兼容性更高的MP3格式呢?下面介绍了三种常用的方法。 方法一&#xf…

ChatGPT:如何安装使用插件?超详细的教程!

1.最简单的方法 直接使用油猴,里边能搜索到的插件都可以用 2.官方插件使用 ChatGPT Plus引入插件后,功能暴强许多,比如可以联网、可以生成图表、可以分析视频、可以与PDF交谈等。但有不少小伙伴还不知道怎么安装使用ChatGPT插件,所…

【分享贴】需求变更、项目延误,项目经理应该如何应对?

案例分享: 项目经理小李跟进了一年半的项目,眼看着要到交付验收的阶段了,甲方的对接人却临时更换了,现在面临着一系列他无法处理的问题,项目目前推进困难。 案例背景: 小李在跟原甲方对接人合作时&#x…

【MySQL】rank()、row_number()、dense_rank()用法详解

建表测试 测试表数据:test1 CREATE DATABASE /*!32312 IF NOT EXISTS*/db_test /*!40100 DEFAULT CHARACTER SET utf8 */; USE db_test; /*Table structure for table test1 */ DROP TABLE IF EXISTS test1; CREATE TABLE test1 ( id int(10) NOT NULL, score i…

十个使用Spring Cloud和Java创建微服务的实践案例

在使用Java构建微服务时,许多人认为只要学习一些微服务设计模式就足够了,比如CQRS、SAGA或每个微服务一个数据库。虽然这是正确的,但同时学习一些通用的最佳实践也是很有意义的。本文分享一些最佳实践。 1 设计模块化的微服务 微服务应该专…

【Git】git的安装与使用教程

【Git】git的安装与使用教程 1.简介1.1.什么是Git1.2.Git与SVN的区别 二、安装Git三、注册Gitee帐号四、使用Git进行上传与下载代码五、使用Git代码冲突六、Git常用命令 1.简介 1.1.什么是Git Git是一个分布式版本控制系统,用于跟踪和管理项目代码的变更。它可以记…

【KingbaseES】R6 Liunx下使用命令行部署数据库集群

【KingbaseES】R6命令行部署数据库集群 A.数据库安装包下载软件下载页面授权下载页面 B.数据库集群部署软件安装第一步:创建Kingbase用户第二步:上传安装包1.创建Kingbase用户和准备安装目录2.使用FTP工具上传安装包镜像和授权文件到install目录下并授权…

【Truffle】四、通过Ganache部署连接

目录 一、下载安装 Ganache: 二、在本地部署truffle 三、配置ganache连接truffle 四、交易发送 除了用Truffle Develop,还可以选择使用 Ganache, 这是一个桌面应用,他同样会创建一个个人模拟的区块链。 对于刚接触以太坊的同学来说&#x…

【ATTCK】MITRE Caldera 朴素贝叶斯规划器

CALDERA是一个由python语言编写的红蓝对抗工具(攻击模拟工具)。它是MITRE公司发起的一个研究项目,该工具的攻击流程是建立在ATT&CK攻击行为模型和知识库之上的,能够较真实地APT攻击行为模式。 通过CALDERA工具,安全…

个人怎么投资伦敦金?

伦敦金是一种被广泛交易的黄金合约,是投资者参与黄金市场的一种交易方式。伦敦金投资也是黄金交易中最为方便快捷的一个种类,在黄金交易市场中占有较大的比例,每天都有来自全球各地的投资者参与买卖,是实现财富增益的一个有效途径…

使用电阻检测仪是否能满足生产车间防静电要求

在现代工业生产中,静电对产品质量和人员安全造成的影响越来越受到重视。特别是在电子、半导体、化工等领域,静电问题可能导致产品损坏、人员触电等严重后果。因此,生产车间的防静电工作显得尤为重要。而电阻检测仪作为一种常用的防静电工具&a…

代码随想录算法训练营Day 47 || 198.打家劫舍、213.打家劫舍II、337.打家劫舍 III

198.打家劫舍 力扣题目链接(opens new window) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系…

【开源分享】国内可用的免费安卓GPT语音助手 - 可音量键唤起,可联网

写在前面:这是一个我写的开源GPT语音助手,不收钱,只求Star! 简要介绍 这是一个基于ChatGPT的安卓端语音助手,允许用户通过手机音量键从任意界面唤起并直接进行语音交流,用最快捷的方式询问并获取回复 使用效果 一、基…

Hadoop学习总结(使用Java API操作HDFS)

使用Java API操作HDFS,是在安装和配置Maven、IDEA中配置Maven成功情况下进行的,如果Maven安装和配置不完全将不能进行Java API操作HDFS。 由于Hadoop是使用Java语言编写的,因此可以使用Java API操作Hadoop文件系统。使用HDFS提供的Java API构…