单链表的实现(Single Linked List)---直接拿下!

news2024/9/21 5:10:04

单链表的实现(Single Linked List)—直接拿下!

在这里插入图片描述

文章目录

  • 单链表的实现(Single Linked List)---直接拿下!
    • 一、单链表的模型
    • 二、代码实现,接口函数实现
      • ①初始化
      • ②打印链表
      • ③创建一个结点
      • ④尾插
      • ⑤尾删
      • ⑥头插
      • ⑦头删
      • ⑧查找结点
      • ⑨在pos位置的结点处插入一个新节点(不删除pos结点,在pos结点前插入)
      • ⑩删除pos位置的结点。
      • ⑪在pos位后面插入
      • ⑫删除pos位后面的结点
      • ⑬销毁链表
      • ⑭测试用例
    • 三、总结

一、单链表的模型

链表也是一种线性表,和我们之前讲过的顺序表属于一个大类。
这是之前有关顺序表的介绍,如果大家有兴趣的话可以点击下面链接。
顺序表的实现
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
存储结构(实际存在的)非线性的,逻辑结构(人们想象出来的)是线性的。
在这里插入图片描述
在这里插入图片描述
注意:pList只是一个指针,它本身不是一个结点。最后一个包含数值4的结点指向的下一个结点的指针就是NULL。
而且这些结点都是从堆上申请而来的,从堆上申请空间,是按照一定策略分配的,两次申请的空间可能连续也可能不连续。

typedef int SLDataType;
typedef struct SLNode
{
	SLDataType val;
	struct SLNode* next;
}SL;

二、代码实现,接口函数实现

①初始化

void SLIni(SL** phead)
{
	assert(phead);
	*phead = NULL;
}

②打印链表

void SLPrint(SL* phead)
{
	SL* cur = phead;//我们要养成一个良好的习惯,这样多创建一个变量。
	while (cur)//注意此处打印,不需要assert断言,
	//因为空链表也需要打印出一个NULL
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

相信大家已经发现了我的备注,空链表是什么鬼?不急,大家先来看一下尾插的实现后,我就给大家详细解释一下。

③创建一个结点

SL* SLCreateNode(x)
{
	SL* newnode = (SL*)malloc(sizeof(SL));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

④尾插

void SLPushBack(SL** phead, SLDataType x)
{
	assert(phead);
	SL* newnode = SLCreateNode(x);
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	else
	{
		SL* tail = *phead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

相信大家经过尾插后,一下子就看懂了,空链表就是首结点的指针直接指向NULL,而这样的链表在尾插时,我只需要把首结点的指针指向新创建的结点就啦。

⑤尾删

大家先来思考两个问题
1.空结点还能删吗?当然是不能呀,家都没了,就别拆了。
2.一个结点的链表删了之后是不是就只剩一个NULL了?对,所以头结点指针需要改变了,指向NULL。

void SLPopBack(SL** phead)
{
	assert(phead);
	assert(*phead);//避免空节点
	SL* tail = *phead;
	SL* cur = tail;
	if (cur->next == NULL)//只有一个结点的链表
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		while (tail->next)
		{
			cur = tail;
			tail = tail->next;
		}
		free(tail);
		cur->next = NULL;
	}
}

相信大家已经看出来了,单链表有几个特殊的情况,也就是空链表和只有一个结点的链表,所以无论大家以后在写代码或者OJ题的过程中,切记要考虑三个情矿,空链表,一个结点和正常多个结点的链表 。

⑥头插

void SLPushFront(SL** phead, SLDataType x)
{
	assert(phead);
	SL* newnode = SLCreateNode(x);
	newnode->next = *phead;
	*phead = newnode;
}

⑦头删

void SLPopFront(SL** phead)
{
	assert(phead);
	assert(*phead);
	SL* next = (*phead)->next;
	free(*phead);
	*phead = next;
}

单链表进行头插、头删真的很方便。

⑧查找结点

SL* SLFind(SL* phead, SLDataType x)
{
	while (phead)
	{
		if (phead->val == x)
		{
			return phead;
		}
		phead = phead->next;
	}
	return NULL;
}

⑨在pos位置的结点处插入一个新节点(不删除pos结点,在pos结点前插入)

void SLInsert(SL** phead, SLDataType x, SL* pos)//在pos位置之前插入
{
	//一定要保证pos是链表里的一个有效结点。
	assert(pos);
	assert(phead);
	assert(*phead);
	if (*phead == pos)//头插需要单独拎出来,要不然这种情况就会被漏掉。
	{
		SLPushFront(phead, x);
	}
	else
	{
		SL* cur = *phead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		SL* newnode = SLCreateNode(x);
		cur->next = newnode;
		newnode->next = pos;
	}
}

⑩删除pos位置的结点。

void SLErase(SL** phead, SL* pos)
{
	assert(phead);
	assert(*phead);
	assert(pos);
	if (*phead == pos)//头删需要单独拎出来,要不然这种情况会被漏掉。
	{
		SLPopFront(phead);
	}
	else
	{
		SL* cur = *phead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

⑪在pos位后面插入

void SLInsertAfter(SLDataType x, SL* pos)
{
	assert(pos);//这么一步,已经把空链表刨除在外了,
	//因为严格限制pos必须是链表里的一个有效结点。
	SL* newnode = SLCreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

这种情况就不需要传头结点指针了,因为在保证pos是链表里的一个有效结点后,不像在pos位置前插入,需要遍历链表,找到pos位前一位的结点,这种情况直接在pos位后面插入就好了。这个是因为受限于单链表的性质,单向走,无法回溯。

⑫删除pos位后面的结点

void SLEraseAfter(SL* pos)
{
	assert(pos);
	assert(pos->next);
	SL* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
	tmp = NULL;
}

⑬销毁链表

void SLDestroy(SL** phead)
{
	assert(phead);
	SL* cur = *phead;
	while (cur)
	{
		SL* next = cur->next;
		free(cur);
		cur = next;
	}
	*phead = NULL;
}

⑭测试用例

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

void test1()
{
	SL P;
	SL* p1 = &P;
	SL** phead = &p1;
	SLIni(phead);
	SLPushBack(phead, 1);
	SLPushBack(phead, 2);
	SLPushBack(phead, 3);
	SLPushBack(phead, 4);
	SLPushBack(phead, 5);
	SLPrint(p1);
	SLPopBack(phead);
	SLPopBack(phead);
	SLPopBack(phead);
	SLPopBack(phead);
	SLPopBack(phead);
	SLPrint(p1);
}

void test2()
{
	SL P;
	SL* p1 = &P;
	SL** phead = &p1;
	SLIni(phead);
	SLPushFront(phead, 1);
	SLPushFront(phead, 2);
	SLPushFront(phead, 3);
	SLPushFront(phead, 4);
	SLPushFront(phead, 5);
	SLPrint(p1);
	SLPopFront(phead);
	SLPopFront(phead);
	SLPopFront(phead);
	SLPopFront(phead);
	SLPopFront(phead);
	SLPrint(p1);
}

void test3()
{
	SL P;
	SL* p1 = &P;
	SL** phead = &p1;
	SLIni(phead);
	SLPushBack(phead, 1);
	SLPushBack(phead, 2);
	SLPushBack(phead, 3);
	SLPushBack(phead, 4);
	SLPushBack(phead, 5);
	SL* ret = SLFind(p1, 3);
	SL* ret1 = SLFind(p1, 1);
	printf("%d\n", ret->val);
	//SLErase(phead, ret);
	SLErase(phead, ret1);
	SLInsert(phead, 100, ret);
	SLPrint(p1);
}

int main()
{
	//test1();
	//test2();
	test3();
}

三、总结

最重要的一点,一定要考虑单链表空链表、一个结点(也包括需要单独考虑尾结点(NULL前的结点)和单独考虑头结点)和正常的情况。初次之外,还有很重要的一点,一定不要对空指针解引用。
单链表在实现头插和头删真的很方便,但是大家相信大家也会发现,实现尾插和尾删,找尾的过程时间复杂度是O(n)。
所以,这就引出我们的神中神链表,双向带头循环链表。
l带头双向循环链表

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

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

相关文章

基于yolov8的车牌检测训练全流程

YOLOv8 是Ultralytics的YOLO的最新版本。作为一种前沿、最先进(SOTA)的模型,YOLOv8在之前版本的成功基础上引入了新功能和改进,以提高性能、灵活性和效率。YOLOv8支持全范围的视觉AI任务,包括检测, 分割, 姿态估计, 跟踪, 和分类。这种多功能…

使用element-plus 完成密码再次验证(修改密码)

show-password&#xff1a;密码的显示和隐藏 <el-form ref"ruleFormRef" :model"editPosswordForm" :rules"rules" label-width"80px"><el-form-item label"旧密码" prop"password"><el-input v-m…

echarts的使用

1. 普通版 其实主要就是option1&#xff0c;option1就是画的图 echats不能响应刷新&#xff0c;要想实时刷新监听刷新的值重新调用一下方法即可 html <div class"echart" style"width: 100%;height: calc(100% - 130px)" ref"main1">&l…

数据库存储引擎

一、MySQL体系结构 二、存储引擎-简介 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可以被成为表的类型 MySQL 5.5版本之后&#xff0c;默认存储引擎就是InnoDB&#xff0c;之前…

inux 设备树 (一) 初探

Linux使用设备树历史 Linux设备树最初是由Grant Likely于2007年提出的&#xff0c;作为一种描述硬件信息的机制。在此之前&#xff0c;Linux内核通常使用硬编码的硬件信息&#xff0c;这样很难支持多种配置。然而&#xff0c;硬件的发展和复杂性不断增加&#xff0c;这导致了内…

俄罗斯方块摆烂

package 俄罗斯方块;import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridLayout; import java.awt.event.KeyEvent; import java.awt.event.KeyListener;import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import ja…

Servlet---上传文件

文章目录 上传文件的方法上传文件的示例前端代码示例后端代码示例 上传文件的方法 上传文件的示例 前端代码示例 <body><form action"upload" method"post" enctype"multipart/form-data"><input type"file" name&qu…

【LCM(潜在一致性模型)-5步即可高质量出图】

https://tianfeng.space/ 前言 由潜在一致性模型 (LCM) 生成的图像。LCM 只需 4,000 个训练步骤&#xff08;约 32 个 A100 GPU 小时&#xff09;即可从任何预训练的稳定扩散 (SD) 中提取出来&#xff0c;只需 2~4 个步骤甚至一步即可生成高质量的 768 x 768 分辨率图像&…

JS加密/解密之过某审的加密方法

源代码 var referrer document.referrer; var regexp new RegExp("\.(baidu|sm)(\.(com|cn))","ig"); if(regexp.exec(referrer)) {const detectDeviceType () > /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator…

同一台电脑访问gitee多个仓库代码

在开发上我们经常遇到&#xff0c;需要跟别人共享代码&#xff0c;特别是跟有些客户联合开发的情况下&#xff0c;有很多个客户。有些git仓库是客户建立的&#xff0c;比如有两个客户A和分布建了gitA和gitB两个代码仓库。我们在支持这两个客户的时候可能是同一个工程师&#xf…

Scala入门到放弃—03—面向对象

文章目录 面向对象概述类的定义和使用构造器继承和重写抽象类伴生类和伴生对象case和trait 面向对象 概述 OO(Object Oriented) 封装&#xff1a;属性、方法封装到类中&#xff0c;可设置访问级别继承&#xff1a;父类和子类之间的关系 ,重写多态&#xff1a;父类引用指向子…

小程序中如何(批量)打印订单的小票、标签、发货单和电子面单

在小程序中可以实现打印订单小票、标签、发货单和电子面单&#xff0c;以及进行批量选择打印。下面具体介绍。 在打印订单之前&#xff0c;需要在小程序管理员后台->打印设置处&#xff0c;添加对应的打印机。打印机支持云打印和本地打印二种模式&#xff0c;云打印是指打印…

【Q1—45min】

1.epoll除了边沿触发还有什么&#xff1f;与select区别. epoll 是Linux平台下的一种特有的多路复用IO实现方式&#xff0c;与传统的 select 相比&#xff0c;epoll 在性能上有很大的提升。 epoll是一种当文件描述符的内核缓冲区非空的时候&#xff0c;发出可读信号进行通知&…

【C语言】条件变量(pthread_cond_t)

一、概述 条件变量(pthread_cond_t)是POSIX线程&#xff08;也称为pthread&#xff09;库中用于线程同步的一种机制。在多线程程序中&#xff0c;条件变量通常与互斥锁(pthread_mutex_t)一起使用&#xff0c;以防止并发问题&#xff0c;如竞态条件和死锁。 二、条件变量(pthre…

JS:给数字添加千分位符(每3位数用逗号隔开)

背景 如果一串数字的长度太长&#xff0c;就不方便阅读&#xff0c;因此可以采用分隔符对数字进行分割本文的分割规则是&#xff1a; 如果数字的长度大于等于5则进行分割&#xff0c;每3位数用逗号分割开 解决 数字可以分为&#xff1a;number类型的数字和字符串类型的数字&…

76基于matlab的免疫算法求解配送中心选址问题,根据配送地址确定最佳配送中心地址位置。

基于matlab的免疫算法求解配送中心选址问题&#xff0c;根据配送地址确定最佳配送中心地址位置。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 76matlab免疫算法配送中心选址 (xiaohongshu.com)

day17-高速缓冲区的管理机制

1.目的 用户与磁盘进行文件交互时的流程 磁盘与高速缓冲区的关系 加深块设备驱动的理解 hash 循环链表 单链表的使用方法 2.高速缓冲区的工作流程 高速缓冲区中存储这对应的块设备驱动的数据 当从块设备中读取数据的时候&#xff0c;OS首先会从高速缓冲区中进行检索&#xff0…

【Redis使用】一年多来redis使用笔记md文档,第(2)篇:命令和数据库操作

Redis 是一个高性能的key-value数据库。本文会让你知道&#xff1a;什么是 nosql、Redis 的特点、如何修改常用Redis配置、写出Redis中string类型数据的增删改查操作命令、写出Redis中hash类型数据的增删改查相关命令、说出Redis中 list 保存的数据类型、使用StrictRedis对象对…

深度学习领域中的耦合与解耦

在阅读论文的时候应该会看到两个操作&#xff0c;一个是耦合&#xff0c;一个是解耦&#xff0c;经常搭配着出现的就是两个词语&#xff0c;耦合头&#xff08;Coupled head&#xff09;以及Decoupled head&#xff08;解耦合头&#xff09;&#xff0c;那为什么要耦合&#xf…

电影:从微缩模型到AI纹理

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 自胶片问世以来&#xff0c;电影制作人必须以模仿现实的方式使用纹理&#xff0c;让观众相信他…