线性表之-单向链表(无头)

news2024/12/23 23:08:02

目录

什么是单向链表

顺序表和链表的区别和联系

顺序表:

链表:

链表表示(单项)和实现

1.1 链表的概念及结构

1.2单链表(无头)的实现

所用文件

将有以下功能:

链表定义

创建新链表元素

尾插

头插

尾删

头删

查找-给一个节点的指针

pos位置之前插入

删除pos位置的值

成品展示

SList.h

SList.c

test.c


什么是单向链表

单向链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。每个节点只能访问它后面的节点,而不能访问前面的节点。

单向链表的特点:

  • 每个节点包含数据和指向下一个节点的指针。
  • 最后一个节点的指针指向空值(NULL),表示链表的结束。
  • 可以动态地添加或删除节点,链表的长度可以根据需要进行扩展或缩小。
  • 可以根据指针迅速插入或删除节点,而不需要移动其他节点。

单向链表相对于数组来说,具有一些优点和缺点:

  • 优点:插入和删除元素的时间复杂度为O(1),不需要像数组一样进行元素的移动;链表长度可以动态调整,没有固定大小的限制。
  • 缺点:要访问特定位置的元素需要从头开始遍历,时间复杂度为O(n);相比于数组,在使用额外的指针存储下一个节点的信息,会占用更多的内存空间。

由于单向链表的特点,它常常被用于需要频繁插入和删除元素的场景,或者在事先无法确定数据大小和数量的情况下使用。

顺序表和链表的区别和联系

顺序表:

优点:
空间连续、支持随机访问
缺点:

  1. 中间或前面部分的插入删除时间复杂度O(N)
  2. 2.增容的代价比较大。

链表:

缺点:
以节点为单位存储,不支持随机访问
优点:

  1. 任意位置插入删除时间复杂度为O(1)
  2. 没有增容消耗,按需申请节点空间,不用了直接释放。

链表表示(单项)和实现

1.1 链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的

1.2单链表(无头)的实现

 

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
    构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
    是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
    来很多优势,实现反而简单了,后面我们代码实现了就知道了。

所用文件

定义三个文件:

  1. 头文件 SList.h
  2. 函数的实现SList.c
  3. 代码的测试test.c

将有以下功能:

//打印链表
void SListPrint(SLTNode* phead);

//创建新链表元素(动态申请一个节点)
SLTNode* BuySListNode(SLTDataType x);

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头删
void SListPopFront(SLTNode** pphead);

//查找->可在查找的基础上进行修改SListAlter
SLTNode* SListFind(SLTNode* phead,SLTDataType x);

//改
void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);

//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);

链表定义

定义链表基本结构

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;

创建新链表元素

创建新元素用于插入原链表

SLTNode* BuySListNode(SLTDataType x)
{
	//开辟空间
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

尾插

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	//开辟空间
	SLTNode* newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		//防止开始时节点为空
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* tail = *pphead;//找到链表首元素
		while (tail->next != NULL)
		{
			//检索到未节点
			tail = tail->next;
		}
		//插入
		tail->next = newnode;
	}
}

头插

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;//地址传给pphead  
	//*pphead=&plist
	
	/*
		头插无需检查是否为空
	*/
}

尾删

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next==NULL)
	{
		//1,只有一个节点

		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2,有多个节点
		
		//将前一个链元素中存放的地址换为NULL,防止野指针
		/* 写法一 */
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next!=NULL)
		{
			tailPrev = tail;//tail的地址
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;

		/* //写法二
		* //tail寻找的是倒数第二个元素
		while (tail->next->next!=NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
		*/
	}
}

头删

void SListPopFront(SLTNode** pphead)
{ 
	//防止被删空
	assert(*pphead);
	
	//找到首位链表元素
	SLTNode* next = (*pphead)->next;//存储首元素存放下一个元素的地址
	free(*pphead);//释放首元素
	*pphead = next;//将第二位元素改为首元素
}

查找-给一个节点的指针

//无需更改元素,故传一级指针
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data==x)
			return cur;
		cur = cur->next;
	}
	//未找到指定元素,返回NULL
	return NULL;
}

改元素是建立再查找基础之上进行更改

void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
{
	printf("修改成功:\n");
	//先找到相应元素,再进行更改
	SLTNode* ret = SListFind(phead, y);
	ret->data = x;
}

pos位置之前插入

任意位置之前插入

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//头插
	if (pos == *pphead)
		SListPushFront(pphead, x);
	else
	{ 
		//任意位置之前插入
		SLTNode* prev = *pphead;
		while (prev->next!=pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
		}
		//找到位置
		SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
		prev->next = newnode;//给前一个元素赋上下一元素地址
		newnode->next = pos;//给插入元素存放下一个元素的地址
	}
}

删除pos位置的值

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
		SListPopFront(pphead);//头删
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
			//移到pos前一位,next存放的是pos的地址
		}
		//将prev存放的地址改为pos后一个元素的地址
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;
	}
}

成品展示

SList.h

#pragma once

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

typedef int SLTDataType;

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;

//打印链表
void SListPrint(SLTNode* phead);

//创建新链表元素(动态申请一个节点)
SLTNode* BuySListNode(SLTDataType x);

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头删
void SListPopFront(SLTNode** pphead);

//查找->可在查找的基础上进行修改SListAlter
SLTNode* SListFind(SLTNode* phead,SLTDataType x);

void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);

//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);

SList.c

#include "SList.h"
 
//打印
void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur!=NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//存放下一个元素的地址
	}
	printf("NULL\n");
}

//创建新链表元素
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = BuySListNode(x);//data

	if (*pphead == NULL)
	{
		//防止开始时节点为空
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			//存放新节点地址
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;//地址传给pphead  
	//*pphead=&plist
	
	/*
		头插无需检查是否为空
	*/
}

//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	
	if ((*pphead)->next==NULL)
	{
		//1,只有一个节点

		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2,有多个节点

		//将前一个链元素中存放的地址换为NULL,防止野指针
		/* 写法一 */
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next!=NULL)
		{
			tailPrev = tail;//tail的地址
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;

		/* //写法二
		* //tail寻找的是倒数第二个元素
		while (tail->next->next!=NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
		*/
	}
}

//头删
void SListPopFront(SLTNode** pphead)
{ 
	//防止被删空
	assert(*pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

//查找-给一个节点的指针
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data==x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

//改
void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
{
	assert(phead);
	printf("修改成功:\n");
	SLTNode* ret = SListFind(phead, y);
	ret->data = x;
}
//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//头插
	if (pos == *pphead)
		SListPushFront(pphead, x);
	else
	{ 
		SLTNode* prev = *pphead;
		while (prev->next!=pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
		}
		//找到位置
		SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
		prev->next = newnode;//给前一个元素赋上下一元素地址
		newnode->next = pos;//给插入元素存放下一个元素的地址
	}
}

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
		SListPopFront(pphead);//头删
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
			//移到pos前一位,next存放的是pos的地址
		}
		//将prev存放的地址改为pos后一个元素的地址
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;
	}
}

test.c

test.c仅仅是用于测试代码,本文以弄懂单向链表为主,故不进行菜单制作
但改测试中也包含了对部分链表结构即原理进行了讲解,请耐心看完

#include "SList.h"
void TestSList1()
{	
	SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n1);

	SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n2);

	SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n3);

	SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n4);
	
	n1->data=1;
	n2->data=2;
	n3->data=3;
	n4->data=4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;
	
	SListPrint(n1);
}

void TestSList2()
{
	SLTNode* plist = NULL;
	//传的是plist指针的地址
	
	//如果直接传plist,将导致SLTNode* phead中
	//phead为临时拷贝,不影响实参
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	
	SListPrint(plist);//不改变无需传二级指针

	SListPushFront(&plist,0);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPrint(plist);
	
	SListPopBack(&plist);
	/*SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);*/
	/*SListPopBack(&plist);
	SListPrint(plist);*/
}
	
void TestSList3()
{
	SLTNode* plist = NULL;
	//传的是plist指针的地址

	//如果直接传plist,将导致SLTNode* phead中
	//phead为临时拷贝,不影响实参
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);

	SListPrint(plist);//不改变无需传二级指针


	//查找
	SLTNode* ret = SListFind(plist, 3);
	if (ret)
	{
		//返回值不为空则为找到
		printf("找到了\n");
	}
	SListPrint(plist);
	修改
	//SListAlter(plist, 20, 2);
	//SListPrint(plist);

	//插入
	SLTNode* pos = SListFind(plist, 3);//先要找到再进行更改
	if (pos)
	{
		SListInsert(&plist, pos, 40);
	}
	SListPrint(plist);

	//删除
	pos = SListFind(plist, 2);//先要找到再进行删除
	if (pos)
	{
		SListErase(&plist, pos);
	}
	SListPrint(plist);
}

int main()
{
	TestSList3();
	 
	return 0;
}

单向链表讲解完毕啦!创作不易,如果喜欢请留下个赞吧,感激不尽😊

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

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

相关文章

linux 进程隔离Namespace 学习

一、linux namespace 介绍 1.1、概念 Linux Namespace是Linux内核提供的一种机制&#xff0c;它用于隔离不同进程的资源视图&#xff0c;使得每个进程都拥有独立的资源空间&#xff0c;从而实现进程之间的隔离和资源管理。 Linux Namespace的设计目标是为了解决多个进程之间…

从零编写STM32H7的MDK SPI FLASH下载算法

从零编写STM32H7的MDK SPI FLASH下载算法 - 知乎 Part1前言 当我们要下载编译好的镜像到Flash时&#xff0c;首先要做的一步就是选择合适的Flash下载算法&#xff0c;而这个算法本身就是一个FLM文件&#xff1a; 代码既可以下载到内部flash&#xff0c;也可以下载到外部flash&…

pyinstaller打包exe运行闪退

这里写自定义目录标题 前言问题描述解决过程 前言 闪退原因可能有很多&#xff0c;这里记录下我遇到的问题&#xff0c;简单来说是dll调用错误导致的闪退&#xff0c;因为我的python用的是32位的&#xff0c;但是pyinstaller却是64位的&#xff0c;属于用conda的时候没注意。 …

【C#】C#调用进程打开一个exe程序

文章目录 一、过程二、效果总结 一、过程 新建WinForm程序&#xff0c;并写入代码&#xff0c;明确要调用的程序的绝对路径&#xff08;或相对路径&#xff09;下的exe文件。 调用代码&#xff1a; 这里我调用的另一个程序的路径是&#xff1a; F:\WindowsFormsApplication2…

Python自动化写作神器:告别语法拼写错误的秘诀

概要 写作是一种常见的沟通方式&#xff0c;无论是在学习、工作还是生活中&#xff0c;我们都需要用文字来表达自己的想法和观点。但是&#xff0c;写作并不是一件容易的事情&#xff0c;尤其是当我们使用非母语时&#xff0c;很容易出现语法和拼写错误&#xff0c;影响了文章…

javaScript:DOM中的CSS操作

目录 1.style 属性获取元素写在行间的样式 2.getComputedStyle(元素对象&#xff0c;null)可以获取元素的非行间样式 3.案例&#xff08;定义一个div和按钮&#xff0c;每点击一次按钮div宽度增加&#xff09; 效果预览图 代码实现 在 JavaScript 中&#xff0c;可以通过…

【8章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第8章 Spark MLlib&#xff08;6节&#xff09; 机器学习算法库 &#xff08;一&#xff09;MLlib简介 1、机器学习 机器学习可以看做是一门人工智能的科学&#xff0c;该领…

【前端面试】快来看看这8个高级面试题

目录 前言1、仔细观察 和 - 运算符2、复制数组元素3、原型和__proto__之间的区别4、范围5、对象强制6、理解对象键7、运算符8、闭包 前言 JavaScript 是一种功能强大的语言&#xff0c;是网络的主要构建块之一。这种强大的语言也有一些怪癖。例如&#xff0c;您是否知道 0 -…

ES查询报错内容长度超过104857600

项目场景&#xff1a; 使用 ElasticsearchRestTemplate 或者使用 RestHighLevelClient 查询 ES 报错 内容长度超过 104857600 问题描述 ES 查询报错 entiity content is too long xxx for the configured buffer limit 104857600 Overridepublic void esQuery() {restHighL…

能耗管理+分区温控成为开发节能、省电神器的关键!从此告别电费刺客时代

取暖器在人们脑海中最深刻的印象&#xff0c;就是费电&#xff01;而它耗电量大的原因&#xff0c;主要在于它是靠电能直接转化为热能&#xff1a;在取暖设备通电后&#xff0c;内部高电阻的电热丝发热&#xff0c;风机会将这股热量吹散到室内&#xff0c;从而达到全屋取暖的效…

OpenCV基础知识(10)— 人脸识别(人脸跟踪、眼睛跟踪、行人跟踪、车牌跟踪和人脸识别)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别技术&#xff0c;也是计算机视觉重点发展的技术。机械学习算法诞生之后&#xff0c;计算机可以通过摄像头等输入设备自动分析图像中包含的内容信息&#xff0c;随…

SpringCloud从基础到活用(超详细)

一、认识微服务 项目的架构方式有&#xff1a;单体架构、分布式架构、微服务架构- 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;项目架构也从单体架构逐渐演变为现在流行的微服务架构。 - 这些架构之间有怎样的差别呢&#xff1f;1、单体架构 **单体…

Linux设备驱动程序

一、设备驱动程序简介 图1.1 内核功能的划分 可装载模块 Linux有一个很好的特性:内核提供的特性可在运行时进行扩展。这意味着当系统启动 并运行时&#xff0c;我们可以向内核添加功能( 当然也可以移除功能)。 可在运行时添加到内核中的代码被称为“模块”。Linux内核支持好几…

Si24R2F+畜牧 耳标测体温开发资料

Si24R2F是针对IOT应用领域推出的新款超低功耗2.4G内置NVM单发射芯片。广泛应用于2.4G有源活体动物耳标&#xff0c;带实时测温计步功能。相较于Si24R2E&#xff0c;Si24R2F增加了温度监控、自动唤醒间隔功能&#xff1b;发射功率由7dBm增加到12dBm&#xff0c;距离更远&#xf…

聊透 GPU 通信技术——GPU Direct、NVLink、RDMA

最近人工智能大火&#xff0c;AI 应用所涉及的技术能力包括语音、图像、视频、NLP 等多方面&#xff0c;而这些都需要强大的计算资源支持。AI 技术对算力的需求是非常庞大的&#xff0c;虽然 GPU 的计算能力在持续提升&#xff0c;但是对于 AI 来说&#xff0c;单卡的计算能力就…

大模型 Dalle2 学习三部曲(二)clip学习

clip论文比较长48页&#xff0c;但是clip模型本身又比较简单&#xff0c;效果又奇好&#xff0c;正所谓大道至简&#xff0c;我们来学习一下clip论文中的一些技巧&#xff0c;可以让我们快速加深对clip模型的理解&#xff0c;以及大模型对推荐带来革命性的变化。 clip结构 首选…

智慧公厕是对智慧城市“神经末梢”的有效激活,公共厕所实现可感知、可视化、可管理、可控制

在当今科技迅速发展的时代&#xff0c;智慧城市已经成为人们关注的热点话题。作为城市基础设施的重要组成部分&#xff0c;公共厕所也逐渐融入到智慧城市的建设中&#xff0c;成为城市管理的焦点之一。智慧公厕作为智慧城市的“神经末梢”&#xff0c;通过可感知、可视化、可管…

期权开户平台:怎样0门槛开户期权,不懂别乱来!

“期权开户平台有传统券商平台、在线期权分仓开户平台、期权科普馆等。具体可用的期权开户平台会因各地区的监管规定和券商政策而有所不同&#xff0c;下文为大家介绍期权开户平台&#xff1a;怎样0门槛开通期权&#xff0c;不懂别乱来&#xff01;本文来自&#xff1a;期权酱 …

无涯教程-JavaScript - MINUTE函数

描述 MINUTE函数返回时间值的分钟。分钟以整数形式给出,范围为0到59。 语法 MINUTE (serial_number)争论 Argument描述Required/OptionalSerial_number 您想要找到的包含分钟的时间。 可以输入时间 作为引号内的文本字符串(如" 6:45 PM") 为十进制数字(如0.7812…

简易实现QT中的virtualkeyboard及问题总结

文章目录 前言&#xff1a;一、虚拟键盘的实现综合代码 二、为什么选用QWidget而不适用QDialog实现键盘三、从窗体a拉起窗体b后&#xff0c;窗体b闪退问题的探讨四、关闭主窗口时子窗口未关闭的问题 前言&#xff1a; 本文章主要包含四部分&#xff1a; 虚拟键盘的实现&#…