【数据结构之单链表的实现(不带头)】

news2024/9/27 17:35:18

 

1.单链表

1.1概念与结构

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

可以用下图便于理解

节(结)点: 

       与顺序表不同的是,链表里面的每节“车厢”都是独立申请下来的空间,所以我们之为“节/结点”。

       每个节(结)点的组成主要是两部分:当前节点要保存的数据保存下一个节点的地址(指针变量)。           上图中指针变量plist保存的是第一个节点的地址,我们称plist为“指向”第一个节点,如果我们希望plist指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0即可。

       链表中每个节点都是独立申请的(即只需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点的位置才能从当前节点找到下一个节点。

 链表的结构:

假设当前链表中保存的是整型数据,当然我们也可以保存其它同一类型的数据。因此为了便于操作其它数据类型的操作,我们对类型进行重命名。

typedef int SLTDatatype;
typedef struct SlistNode
{
	SLTDatatype data;
	struct SlistNode* next;
}SLTNode;

1.2链表的性质

  • 链式结构在逻辑上是连续的,在物构上不是连续的。
  • 节点一般是从堆上申请的。(涉及动态内存管理)
  • 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,也可能不连续。

2.单链表的实现 

                   实现链表的时候和实现顺序表的时候类似,都需要三个文件,SlistNode.c文件,SlistNode.h文件和test.c(测试文件)。

2.1 SlistNode.h   链表结构的定义以及函数功能的声明部分

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//单链表的结构定义   不带头所以不需要初始化
typedef int SLTDatatype;
typedef struct SlistNode
{
	SLTDatatype data;
	struct SlistNode* next;
}SLTNode;

//函数的声明部分

//单链表的插入
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x);

//单链表的数据的打印
void SLTPrint(SLTNode* phead);

//单链表的删除
//单链表的尾删
void SLTPopBack(SLTNode** pphead);
//单链表的头删
void SLTPopFront(SLTNode** pphead);


//单链表中数据的查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x);



//单链表指定位置的操作
//指定位置之前的插入
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDatatype x);
//指定位置之后数据的插入
void SLTInsertBack(SLTNode* pos, SLTDatatype x);


//指定位置数据的删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//指定位置之后的删除
void SLTEraseAfter(SLTNode* pos);

//链表的销毁
void SListDestroy(SLTNode**pphead);

2.2 SlistNode.c  函数功能的实现

2.2.1 单链表数据的插入:尾插和头插

//申请新节点
SLTNode* SLTBuyNode(SLTDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

实现思路:

         在实现链表的插入之前,需要先封装一个可以申请新节点的函数,由链表的概念以及性质我们只需要用malloc函数进行申请新节点,随后插入时只需要改变节点的的指向关系即可。

//单链表的插入
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
	assert(pphead);
	//链表为空和非空两种情况
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead==NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* pcur = *pphead;
		//先遍历找到尾节点
		while (pcur->next != NULL)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDatatype x)
{
	assert(pphead );
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

实现思路:

       尾插头插之前头需要对所传的二级指针进行判空(检查是否为有效地址),除此之外尾插和头插都需要申请到新节点。

       尾插分为两种情况,一种是链表为空,另一种是链表非空。之所以分成这两种情况是因为在尾插的时候需要遍历链表完之后对当前位置的节点进行解引用,如果为空链表,就会报错(对空链表不能进行访问)。所以链表为空时直接将申请的新节点的地址给*pphead,非空时先让链表进行遍历,遍历到下一个节点为空时停下来,将所申请到新节点的地址给当前节点中的next指针。

       头插:头插很简单,对于空链表和非空链表都可以,先将申请到新节点的next指针指向头结点,再将新节点的地址给头结点。

PS:      我们在这里之所以传的是二级指针,是因为我们在刚开始(见test.c)中申请节点的时候,使用的是一级指针申请的变量,由指针内容我们可以知道要通过函数改变一个指针指向的内容就需要用该变量的指针,在这里该变量是一级指针,所以我们需要用指针的指针即就是二级指针。

2.2.2 单链表数据的删除 :尾删和头删 

//单链表的删除
//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && * pphead);
	//分成两种情况:只有一个节点(不需要遍历链表)  有多个节点
	if ((*pphead)->next == NULL)
	{
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead, * ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next= NULL;
	}
}
//单链表的头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//先把头结点的下一个节点保存下来
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

实现思路:

 尾删头删实现的时候都需要先进行对pphead和*pphead(确保链表非空)进行判空。

尾删:因为链表中只有一个节点的时候不存在前驱节点,则直接将头结点释放掉且置为空。而当链表中有多个节点时,则需要先找到前驱节点释放掉尾节点再将前驱节点的next和尾节点置为空

头删:先将头结点所指向的next保存在next中,然后释放掉头结点,最后将保存的next赋值给头节点。

2.2.3 单链表数据的查找

//单链表中数据的查找
SLTNode* SLTFind(SLTNode* phead, SLTDatatype x)
{
	assert(phead);
	//遍历链表
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

实现思路:先对所传的变量进行判空(是否有效)。再遍历链表,如果当前节点的数据为所要查找的数据,则返回当前节点的地址

2.2.4 单链表指定位置的操作

2.2.4.1 指定位置的插入
//指定位置之前的插入     特别注意
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		//头结点没有前一个节点,故就直接调用头插的方法
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将prev newnode pos连接起来
		newnode->next = prev->next;
		prev->next = newnode;
	}
}
//指定位置之后数据的插入
void SLTInsertBack(SLTNode* pos, SLTDatatype x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//将pos newnode pos->next连接起来
	newnode->next = pos->next;
	pos->next = newnode;
}

实现思路: 指定位置之前的插入(还需要判断pphead的有效性)和指定位置之后插入,都需要对要插入位置pos的有效性进行判断。

指定位置之前的插入:

(1.)当插入的位置恰好为头节点时,因为头结点没有前驱节点)所以直接调用头插的方法即可。

(2.)当插入的位置为其他节点的位置时,先申请一个新节点,然后遍历找到要插入位置之前的前驱节点prev,然后将newnode,prev,pos三个位置的节点连接起来。

指定位置之后的插入:

先判断位置的有效性,再申请一个新节点,最后将pos ,newnode,pos->next位置的节点连接起来。

2.2.4.2 指定位置的删除
//指定位置数据的删除     特别注意
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead&&pos);
	if (*pphead == pos)
	{
		//如果是删除的头结点,头结点没有前驱节点
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//先将申请的节点释放掉,将pos pos->next->next连接起来
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//指定位置之后的删除
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos&&pos->next);
	//先释放pos之后的节点,再将pos pos->next->next 连接起来
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

实现思路:

指定位置的数据的删除:需要先判断pphead和*pphead的有效性以及pos位置的有效性。(1.)如果要删除的是头结点不存在前驱节点),则只需要调用头删即可。

(2.)如果删除的不是头结点的位置,则需要先遍历链表找到要删除位置的前驱节点,然后先将删除位置的下一个节点保存下来,其次将pos前后位置的节点连接起来再释放掉要删除位置的节点,将pos置为空。

指定位置之后的数据的删除: 需要先判断pos和pos->next的有效性。

先将pos位置之后的节点保存到del中,然后将pos和del->next连接起来,再释放掉del,最后将del置为空即可。

2.2.5 单链表的销毁

//链表的销毁
void SListDestroy(SLTNode** pphead)
{
	assert(pphead&&*pphead);
	//每次循环释放 每次释放节点时先将下一个节点保存下来
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

实现思路:应先判断pphead和*pphead的有效性。

因为每一个节点都是动态内存申请的,所以我们应该遍历链表一一释放节点。但是需要注意的是每次释放释放节点时,应该先把当前要删除节点的下一个位置保存下来,在释放完之后,将保存的节点的位置赋值给当前节点

希望大家通过 我的博客可以对知识有更新,更深的理解!

 如果有错,还望指出!!!

关注博主,优质内容不断更新!!!

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

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

相关文章

三十种未授权访问漏洞合集

未授权访问漏洞介绍 未授权访问可以理解为需要安全配置或权限认证的地址、授权页面存在缺陷&#xff0c;导致其他用户可以直接访问&#xff0c;从而引发重要权限可被操作、数据库、网站目录等敏感信息泄露。---->目录遍历 目前主要存在未授权访问漏洞的有:NFS服务&a…

百度飞桨 OCR识别

百度飞桨 OCR识别代码 import warnings import time import cv2 as cv import paddlehub as hub # Load the image img cv.imread("1.jpg") height, width, channels img.shape imglist [img] ocr hub.Module(name"ch_pp-ocrv3", enable_mkldnnTrue) …

从Axure入门,开始了解产品

​不少想要求职产品经理的小伙伴在问一个问题&#xff1a;我是一个纯小白&#xff0c;一点基础都没有&#xff0c;我该如何入门产品呢&#xff1f;当然想要入门产品&#xff0c;很多人都有自己的一套方法&#xff0c;这里推荐其中的一种方法&#xff0c;从原型工具&#xff0c;…

ModuleNotFoundError: No Module Named openai

题意&#xff1a;Python 无法在环境中找到名为 openai 的模块 问题背景&#xff1a; import requests from bs4 import BeautifulSoup import openai #write each line of nuclear.txt to a list with open(nuclear.txt, r) as f:lines f.readlines()#remove the newline cha…

Spring源码-ClassPathXmlApplicationContext的refresh()都做了什么?

AbstractApplicationContext的refresh方法 /*** 用给定的父类创建一个新的ClassPathXmlApplicationContext* Create a new ClassPathXmlApplicationContext with the given parent,* 从给定的XML文件加载定义* loading the definitions from the given XML files.* param confi…

UE5 从零开始制作跟随的大鹅

文章目录 二、绑定骨骼三、创建 ControlRig四、创建动画五、创建动画蓝图六、自动寻路七、生成 goose八、碰撞 和 Physics Asset缺点 # 一、下载模型 首先我们需要下载一个静态网格体&#xff0c;这里我们可以从 Sketchfab 中下载&#xff1a;Goose Low Poly - Download Free …

十条线路:畅享张北草原天路玩法

2024年6月6日&#xff0c;张家口市政府新闻办召开新闻发布会&#xff0c;发布10条草原天路精品旅游线路&#xff0c;同时就草原天路今年改造提升重点工作进行介绍。其中&#xff0c;10条精品旅游线路包含5条玩转天路经典线路和5条穿越天路新玩法线路。 1、寻“天路之巅”网红打…

Java并发编程 使用锁和状态位来控制线程的执行顺序

Java线程生命周期的认识 对于线程的生命周期&#xff0c;在Java和操作系统中&#xff0c;在概念上有一点小小的不同。 在操作系统层面上&#xff0c;线程的生命周期如下&#xff1a; 1.新建 2.就绪 3.阻塞 4.运行 5.终止 而在Java层面上&#xff0c;则把线程的阻塞状态又划分…

详细分析Flask部署云服务器(图文介绍)

目录 前言1. 安装配置2. 代码部署3. 服务配置4. 自启动前言 Nginx信息补充阅读: Nginx从入门到精通(全)Nginx配置静态网页访问(图文界面)本文着重提供思路逻辑 1. 安装配置 最好的方式是安装docker,通过docker安装nginx,推荐阅读:Docker零基础从入门到精通(全)包环…

与用户有关的接口

1.获取用户详细信息 跟着黑马程序员继续学习SpringBoot3Vue3 用户登录成功之后跳转到首页&#xff0c;需要获取用户的详细信息 打开接口文档 使用Token令牌解析得到用户名 我们需要根据用户名查询用户&#xff0c;获取详细信息 但是请求参数是无&#xff0c;由于都需要携…

标题生成器:开启创意写作的新篇章

文章目录 角色与目标标题生成器的功能标题生成器的优势指导原则限制与澄清应用场景对创意写作的影响智能体发布到微信公众号配置公众号菜单配置自动回复自动回复文本链接自动回复二维码图片 标题生成器的未来发展总结 博主介绍&#xff1a;全网粉丝10w、CSDN合伙人、华为云特邀…

C++入门基本语法(1)

一、命名空间namespace 定义变量、函数时&#xff0c;定义的名称可能会和头文件中或者自己重复使用的名称冲突&#xff1b;namespace可以对标识符的名称进行本地化&#xff0c;以避免冲突的问题&#xff1b; ## 例如&#xff1a; ## 出现这种问题的原因&#xff1a; &#x…

MySQL系列之--详细安装教程和启动方法

文章目录 安装教程打开或关闭方式方式1&#xff1a;方式2&#xff1a; 客户端连接方式客户端连接方式1&#xff1a;客户端连接方式2&#xff1a;MySQL环境变量的配置 安装教程 1、mysql官网下载最新的符合本系统的版本 2、点击.msi文件进入安装页面 选择默认的选项开发者安…

品味食家巷蛋奶酪饼,感受西北美食魅力

在广袤的西北大地&#xff0c;美食的丰富多样令人叹为观止。而食家巷蛋奶酪饼&#xff0c;宛如一颗璀璨的明珠&#xff0c;散发着独特的魅力。 这款蛋奶酪饼&#xff0c;是传统工艺与现代口味的完美融合。而当你继续品尝&#xff0c;鸡蛋的鲜嫩和奶酪的浓郁醇厚便会在口中交融…

跟《经济学人》学英文:2024年08月03日这期 GPT, Claude, Llama? How to tell which AI model is best

GPT, Claude, Llama? How to tell which AI model is best Beware model-makers marking their own homework 原文&#xff1a; When Meta, the parent company of Facebook, announced its latest open- source large language model (LLM) on July 23rd, it claimed that…

vue2 使用 tinymce富文本编辑器

注意&#xff1a; 在vue2中使用tinymce有版本限制的&#xff0c;最新版都是支持v3的&#xff0c;官方也说明了&#xff1b; vue2中不能使用tinymce/tinymce-vue 为4以上的版本&#xff1b; 使用步骤&#xff1a; 1、vue项目中安装 tinymce&#xff1b; npm install tinymce5.…

用TensorFlow训练自己的第一个模型

现在学AI的一个优势就是&#xff1a;前人栽树后人乘凉&#xff0c;很多资料都已完善&#xff0c;而且有很多很棒的开源作品可以学习&#xff0c;感谢大佬们 项目 项目源码地址 视频教程地址 我在大佬的基础上基于此模型还加上了根据特征值缓存进行快速识别的方法&#xff0c;…

【教程】Python语言的地球科学常见数据——全球大气再分析数据

a、多年数据的读取 b、趋势分析 c、多时间尺度统计。 ECMWF 中心推出的 ERA5 全球大气再分析数据提供了大量大气、陆地和海洋气候变量的逐小时数据。这些数据在 30km 网格上覆盖了全球&#xff0c;在时间跨度上从 1979 至今。该数据能够提供全球范围的格点气象数据。 将针对该…

react-native从入门到实战系列教程一Switch组件和StatusBar的运用

跨平台通用的组件。这是一个受控组件&#xff0c;你必须使用onValueChange回调来更新value属性以响应用户的操作。如果不更新value属性&#xff0c;组件只会按一开始给定的value值来渲染且保持不变&#xff0c;看上去就像完全点不动。 实现效果 代码实现 import {View, Text,…

力扣hot100-二叉树

文章目录 概要二叉树的基本概念常见的二叉树类型常用的二叉树遍历二叉树的常用技巧 题目&#xff1a;二叉树的中序遍历方法1--递归遍历方法2--使用栈 概要 二叉树&#xff08;Binary Tree&#xff09;是一种树形数据结构&#xff0c;其中每个节点最多有两个子节点&#xff0c;…