数据结构--单链表(图文)

news2024/11/25 23:43:21

单链表的概念

在单链表中,每个元素(称为节点)包含两部分:一部分是存储数据的数据域,另一部分是存储下一个节点地址的指针域。这里的“单”指的是每个节点只有一个指向下一个节点的指针。
在这里插入图片描述

  1. 节点:链表中的基本单元,通常由数据域和指针域组成。数据域用于存储实际的数据,而指针域用于指向链表中的下一个节点。

  2. :通过节点中的指针链接形成的序列。在单链表中,这种链接是单向的,即只能从一个节点指向下一个节点。

  3. 头节点:链表的第一个节点,用于标识链表的开始。有时,头节点仅作为链表的头部标识,不存储实际数据。

  4. 尾节点:链表的最后一个节点,其指针域通常为空(NULL),表示链表的结束。

  5. 非连续性:与数组不同,单链表的节点在内存中不必连续存储。每个节点的位置由其前一个节点的指针决定。

  6. 动态性:单链表的大小是动态的,可以在运行时通过增加或删除节点来改变链表的长度。

单链表的主要特点是其灵活性和动态性,它可以有效地进行插入和删除操作,尤其是在不知道数据数量的情况下,或者当数据量变化较大时。但是,由于需要通过指针进行遍历,单链表在访问特定元素时可能不如数组高效。以下是其在各种操作中的优势:

  • 插入和删除:在单链表中插入或删除节点只需要O(1)的时间,前提是已经有了指向要操作节点的指针。
  • 空间利用:单链表不需要预分配固定的存储空间,它可以根据需要动态地分配内存。

单链表的实现

首先创建三个文件:

  • SList.h —— 用于声明函数的头文件
  • SList.c —— 单链表主要函数的实现
  • test.c——测试单链表。

创建单链表

typedef int SLTDataType;//方便后续使用更改类型

//创建一个节点
typedef struct SListNode
{
	SLTDataType data;//该节点储存的数据
	struct SListNode* next;//指向下一个节点的指针
}SListNode;

销毁链表

从前向后依次释放节点,最后将头节点置为NULL 。

void SListDesTroy(SListNode** pphead)//销毁链表
{
	assert(pphead && *pphead);
	SListNode* pcur = *pphead;
	while (pcur)
	{
		SListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

打印节点数据

遍历依次打印即可。

void SLTPrint(SListNode* phead)//打印节点数据
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

申请一块节点

在进行插入节点时,我们需要先申请一块节点来进行插入,所以我们将申请节点单独封装成一个函数。

SListNode* SLTBuyNode(SLTDataType x)//增加节点(空间)
{
	//开辟一个节点空间
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	//设置数据并返回该节点地址
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

尾插数据

两种情况

  • 情况一:链表为空直接申请新节点给头节点;
  • 情况二:链表不为空,遍历找到尾节点,再将新节点接入即可。

在这里插入图片描述

void SLTPushBack(SListNode** pphead, SLTDataType x)//尾插数据
{
	assert(pphead);//不能为空,否则就会对空指针解引用
	if (*pphead == NULL)//指向头节点的指针为空,也就是链表为空
	{
		*pphead = SLTBuyNode(x);
	}
	else
	{
		SListNode* newnode = *pphead;
		while (newnode->next)
		{
			newnode = newnode->next;
		}
		newnode->next = SLTBuyNode(x);
	}
}

头插数据

两种情况

  • 情况一:链表为空直接申请新节点给头节点。
  • 情况二:链表不为空,先将pphead(头节点)保存起来,然后让pphead指向新插入的节点,在让*pphead->next指向刚才保存好的原本的头节点。
    在这里插入图片描述
void SLTPushFront(SListNode** pphead, SLTDataType x)//头插数据
{
	assert(pphead);
	if (*pphead == NULL)
	{
		*pphead = SLTBuyNode(x);
	}
	else
	{
		SListNode* pcur = *pphead;
		*pphead = SLTBuyNode(x);
		(*pphead)->next = pcur;
	}
}

尾删数据

两种情况

  • 情况一:链表只有一个数据,直接释放该节点并将头节点置为空。
  • 情况二:链表有多个数据节点,遍历找到尾节点的前一个节点,释放该节点的next节点,再将该节点的next置为空。
    在这里插入图片描述
void SLTPopBack(SListNode** pphead)//尾删数据
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next->next)
		{
			pcur = pcur->next;
		}
		free(pcur->next);
		pcur->next = NULL;
	}
}

头删数据

两种情况

  • 情况一:链表只有一个数据,直接释放该节点并将头节点置为空。
  • 情况二:链表有多个数据节点,先将pphead(头节点)保存起来,然后让pphead指向*pphead->next(新的头节点),然后释放刚才保存的节点(原头节点)。
    在这里插入图片描述
void SLTPopFront(SListNode** pphead)//头删数据
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* pcur = (*pphead);
		*pphead = (*pphead)->next;
		free(pcur);
	}
}

查找数据

从前向后遍历整个链表,如果 plist->data == x,就说明找到了,返回 plist 此时的值,如果plist = NULL了,就说明这个链表中没有该数据,则返回空。

SListNode* SLTFind(SListNode* phead, SLTDataType x)//查找数据
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

有了查找函数,就可以实现任意位置的增加数据和删除数据的操作了。

在指定位置之前插入数据

两种情况

  • 情况一:指定位置为头节点则直接头插。
  • 情况二:指定位置为其他节点,遍历找到指定节点的前一个节点,在该节点后插入新数据。
    在这里插入图片描述
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x)//在指定位置之前插入数据
{
	assert(pphead&&*pphead);
	if (*pphead == pos)
	{
		SLTPushFront(pphead,x);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		SListNode* newnode = SLTBuyNode(x);
		pcur->next = newnode;
		newnode->next = pos;
	}
}

在指定位置之后插入数据

在指定位置后直接插入即可。

void SLTInsertAfter(SListNode* pos, SLTDataType x)//在指定位置之后插入数据
{
	assert(pos);
	SListNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除指定位置节点

两种情况

  • 情况一:指定位置为头节点则直接头删。
  • 情况二:指定位置为其他节点,遍历找到指定位置的前一个节点,先将该节点的next指针指向指定位置的next位置,然后再释放指定位置。(不能改变顺序,否则找不到指定位置的下一个节点了)
    在这里插入图片描述
void SLTErase(SListNode** pphead, SListNode* pos)//删除pos节点
{
	assert(*pphead && pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除指定位置之后的数据

先将指定位置的的下一个位置保存起来,让指定位置的next指针指向保存位置的下一个位置,然后释放掉保存的位置也就是指定位置之后的数据。
在这里插入图片描述

void SLTEraseAfter(SListNode* pos)//删除pos之后的节点
{
	assert(pos && pos->next);
	SListNode* pcur = pos->next;
	pos->next = pos->next->next;
	free(pcur);
	pcur = NULL;	
}

单链表源码

SList.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SLTDataType;

//创建一个节点
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SListNode;

SListNode* SLTBuyNode(SLTDataType x);//增加节点(空间)


void SLTPrint(SListNode* phead);//打印节点数据


void SListDesTroy(SListNode** pphead);//销毁链表


void SLTPushBack(SListNode** pphead, SLTDataType x);//尾插数据


void SLTPushFront(SListNode** pphead, SLTDataType x);//头插数据


SListNode* SLTFind(SListNode* phead, SLTDataType x) ;//查找数据


void SLTPopBack(SListNode** pphead);//尾删数据


void SLTPopFront(SListNode** pphead);//头删数据


void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x);//在指定位置之前插入数据


void SLTInsertAfter(SListNode* pos, SLTDataType x);//在指定位置之后插入数据


void SLTErase(SListNode** pphead, SListNode* pos);//删除pos节点


void SLTEraseAfter(SListNode* pos);//删除pos之后的节点


SList.c

#include"SList.h"

SListNode* SLTBuyNode(SLTDataType x)//增加节点(空间)
{
	//开辟一个节点空间
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	//设置数据并返回该节点地址
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SLTPrint(SListNode* phead)//打印节点数据
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
void SListDesTroy(SListNode** pphead)//销毁链表
{
	assert(pphead && *pphead);
	SListNode* pcur = *pphead;
	while (pcur)
	{
		SListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

void SLTPushBack(SListNode** pphead, SLTDataType x)//尾插数据
{
	assert(pphead);
	if (*pphead == NULL)
	{
		*pphead = SLTBuyNode(x);
	}
	else
	{
		SListNode* newnode = *pphead;
		while (newnode->next)
		{
			newnode = newnode->next;
		}
		newnode->next = SLTBuyNode(x);
	}
}



void SLTPushFront(SListNode** pphead, SLTDataType x)//头插数据
{
	assert(pphead);
	if (*pphead == NULL)
	{
		*pphead = SLTBuyNode(x);
	}
	else
	{
		SListNode* pcur = *pphead;
		*pphead = SLTBuyNode(x);
		(*pphead)->next = pcur;
	}
}


SListNode* SLTFind(SListNode* phead, SLTDataType x)//查找数据
{
	assert(phead);
	SListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}


void SLTPopBack(SListNode** pphead)//尾删数据
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next->next)
		{
			pcur = pcur->next;
		}
		free(pcur->next);
		pcur->next = NULL;
	}
}


void SLTPopFront(SListNode** pphead)//头删数据
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* pcur = (*pphead);
		*pphead = (*pphead)->next;
		free(pcur);
	}
}


void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x)//在指定位置之前插入数据
{
	assert(pphead&&*pphead);
	if (*pphead == pos)
	{
		SLTPushFront(pphead,x);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		SListNode* newnode = SLTBuyNode(x);
		pcur->next = newnode;
		newnode->next = pos;
	}
}


void SLTInsertAfter(SListNode* pos, SLTDataType x)//在指定位置之后插入数据
{
	assert(pos);
	SListNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}


void SLTErase(SListNode** pphead, SListNode* pos)//删除pos节点
{
	assert(*pphead && pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}


void SLTEraseAfter(SListNode* pos)//删除pos之后的节点
{
	assert(pos && pos->next);
	SListNode* pcur = pos->next;
	pos->next = pos->next->next;
	free(pcur);
	pcur = NULL;	
}

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

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

相关文章

网络协议安全:TCP/IP协议栈的安全问题和解决方案

「作者简介」:北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖Web安全、系统安全等12个知识域的一百多个知识点,持续更新。 这一章节我们需要知道TCP/IP每…

2024最新最全【网络安全/渗透测试】面试题汇总

思路流程 信息收集漏洞挖掘漏洞利用&权限提升清除测试数据&输出报告复测 问题深信服一面:SQL注入防护为什么参数化查询可以防止sql注入SQL头注入点盲注是什么&#xff1f;怎么盲注&#xff1f;宽字节注入产生原理以及根本原因 产生原理在哪里编码根本原因解决办法sql里…

Kafka中的时间轮算法

1. Kafka与时间轮&#xff1a; Kafka的定时器底层使用时间轮算法。Kafka时间轮是层次时间轮&#xff0c;并且支持时间轮复用。 优点&#xff1a; 高效的插入操作&#xff1a; 时间轮底层数据结构&#xff08;桶&#xff09;&#xff0c;使用双向链表的设计使得插入操作的时间…

掌握JavaScript ES6精髓:探索函数和对象的高级扩展与实用技巧

序言 JavaScript&#xff0c;作为前端开发中不可或缺的语言&#xff0c;已经发展到了ECMAScript 2015&#xff08;简称ES6&#xff09;以及后续的版本。ES6带来了诸多语法上的改进和创新&#xff0c;使得代码更加简洁、优雅&#xff0c;同时也提供了更多的编程模式和实用技巧。…

Python-面向对象编程(超详细易懂)

面向对象编程&#xff08;oop&#xff09; 面向对象是Python最重要的特性&#xff0c;在Python中一切数据类型都是面向对象的。 面向对象的编程思想&#xff1a;按照真实世界客观事物的自然规律进行分析&#xff0c;客观世界中存在什么样的实体&#xff0c;构建的软件系统就存在…

Docker搭建yolov8并训练自己的数据集(包会)

本文通过docker的方式搭建yolov8运行环境&#xff0c;并成功训练了自己的数据集&#xff08;化学仪器数据集&#xff09;。 1、安装docker 在一台通往的服务器上安装docker&#xff0c;建议参考我之前的文章&#xff1a;Docker基础学习-CSDN博客 2、创建yolov8镜像 首先编写…

openresty(Nginx) 301重定向域名 http访问强制使用https

1 访问http 2 修改配置访问 server {listen 80;server_name example.cn;return 301 https://$server_name$request_uri;access_log /data/logs/czgzzfjgsup_access.log access;error_log /data/logs/czgzzfjg_error.log error;#location / {root /usr/local/open…

【Linux进程】Linux下的---七大进程状态(什么是进程状态?Linux下有哪些进程状态?)

目录 一、前言 二、什么是进程状态&#xff1f; 三、操作系统(OS)下的 --- 进程状态 &#x1f525;运行状态&#x1f525; &#x1f525;阻塞状态&#x1f525; &#x1f525;挂起状态&#x1f525; 四、Linux下的7种进程状态 &#x1f525;运行状态 -- R&#x1f525;…

内存优化技巧:让数据处理更高效

Pandas无疑是我们数据分析时一个不可或缺的工具&#xff0c;它以其强大的数据处理能力、灵活的数据结构以及易于上手的API赢得了广大数据分析师和机器学习工程师的喜爱。 然而&#xff0c;随着数据量的不断增长&#xff0c;如何高效、合理地管理内存&#xff0c;确保Pandas Da…

vue中实现百度地图全国与省市地图切换

前言 本文主要是用于示例全国地图&#xff0c;点击省市地图直接跳转到该省市地图并展示&#xff0c;可以拓展在地图上显示标记点&#xff08;本文未做示例&#xff09;&#xff0c;后续有完整代码&#xff0c;但是由于需要与本来项目业务代码进项分割&#xff0c;可能会有些问题…

centos 7无需token编译安装freeswitch 1.10.11 ——筑梦之路

准备工作 安装编译工具和依赖包 yum update -y sudo yum install epel-release vim tcpdump net-tools.x86_64 -y sudo yum install gcc-c sqlite-devel zlib-devel libcurl-devel pcre-devel speex-devel ldns-devel libedit-devel openssl-devel git -y yum install yasm n…

Web APIs--Dom获取属性操作

目录 1.DOM&#xff08;操作网页内容、用户交互&#xff09; 2.DOM对象获取&#xff08;querySelect(‘’)、querySelectAll(‘’)&#xff09; 总结&#xff1a; 3.操作元素内容&#xff08;修改元素的文本更换内容&#xff09; 1. 元素innerText 属性 2.元素.innerHTML…

JavaSE 面向对象程序设计初级 静态static 包package 常量final 代码块 代码实操理论内存原理详解

目录 static(静态) 静态的特点 应用示例 静态变量 静态方法 注意事项 内存图 重新认识main方法 包 什么是包 使用导包在什么时候 final关键字 常量 命名规范 细节&#xff08;重点&#xff09; 权限修饰符 代码块 局部代码块 构造代码块 静态代码块 个人号…

【机器学习】必会降维算法之:随机投影(Random Projection)

随机投影&#xff08;Random Projection&#xff09; 1、引言2、随机投影&#xff08;Random Projection&#xff09;2.1 定义2.2 核心原理2.3 应用场景2.4 实现方式2.5 算法公式2.6 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;降维算法还没讲完呢。 小鱼&a…

EE trade:炒伦敦金的注意事项及交易指南

在贵金属市场中&#xff0c;伦敦金因其高流动性和全球认可度&#xff0c;成为广大投资者的首选。然而&#xff0c;在炒伦敦金的过程中&#xff0c;投资者需要注意一些关键点。南华金业小编带您一起来看看。 国际黄金报价 一般国际黄金报价会提供三个价格&#xff1a; 买价(B…

c++里 父类私有的虚函数,也是可以被子类重写和继承的。但父类私有的普通函数,子类无法直接使用

谢谢 。今天看课本上有这么个用法&#xff0c;特测试一下。这样就也可以放心的把父类的私有函数列为虚函数了&#xff0c;或者说把父类的虚函数作为私有函数了。 再补充一例&#xff1a;

ls命令的参数选项

ls命令的参数的作用 可以指定要查看的文件夹&#xff08;目录&#xff09;的内容&#xff0c;如果不指定参数&#xff0c;就查看当前工作目录的内容。ls 命令的选项 常用语法&#xff1a;ls [-a -l -h] [linux路径] -a 选项表示 all &#xff0c;即列出全部内容&#xff0c;包括…

Linux文件编程详解

Linux文件编程详解 在Ubuntu&#xff08;Linux&#xff09;系统下进行文件操作涉及一系列的系统调用&#xff0c;这些调用是基于Unix风格的文件操作API。这些操作包括打开或创建文件、从文件中读取数据、向文件中写入数据、移动文件指针以及关闭文件。以下是这些函数的详细介绍…

WPF/C#:在DataGrid中显示选择框

前言 在使用WPF的过程中可能会经常遇到在DataGrid的最前或者最后添加一列选择框的需求&#xff0c;今天跟大家分享一下&#xff0c;在自己的项目中是如何实现的。 整体实现效果如下&#xff1a; 如果对此感兴趣&#xff0c;可以接下来看具体实现部分。 实践 假设数据库中的…

LeetCode LCP 61. 气温变化趋势

别怕麻烦&#xff0c;模拟题有时候就是要多写一些条件&#xff08;或者你思维很活跃找出规律&#xff09;&#xff0c;代码如下&#xff1a; class Solution { public:int temperatureTrend(vector<int>& temperatureA, vector<int>& temperatureB) {int …