【数据结构初级(2)】单链表的基本操作和实现

news2024/11/24 17:03:15

文章目录

  • Ⅰ 概念及结构
    • 1. 单链表的概念
    • 2. 单链表的结构
  • Ⅱ 基本操作实现
    • 1. 定义单链表结点
    • 2. 创建新结点
    • 3. 单链表打印
    • 4. 单链表尾插
    • 5. 单链表头插
    • 6. 单链表尾删
    • 7. 单链表头删
    • 8. 单链表查找
    • 9. 在指定 pos 位置前插入结点
    • 10. 删除指定 pos 位置的结点
    • 11. 单链表销毁

本章实现的是不带头结点的单链表。

Ⅰ 概念及结构

1. 单链表的概念

  • 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
  • 单链表的每个结点仅仅要存储该结点本身应该存储的数据,还要存储下一个与自己同类型结点的地址。

在这里插入图片描述

2. 单链表的结构

1. 物理结构

每个单链表结点都有一个指针域,用来存储下一个结点的地址。

在这里插入图片描述

2. 逻辑结构

为了方便理解和学习,使用箭头来表示每个结点的指针域所存储的是哪个结点的地址。

在这里插入图片描述

Ⅱ 基本操作实现

1. 定义单链表结点

  • 每个结点都应该包含数据域和指针域两部分内容。
  • 数据域的数据类型应该使用 typedef 来定义,防止以后更改数据域的数据类型。
typedef int SLTDataType;	//每个单链表结点数据域的数据类型

typedef struct SListNode	//定义一个单链表结点
{
	SLTDataType data;		//单链表的数据域
	struct SListNode* next;	//单链表的指针域,用于存放下一个同类型结点的地址
}SLNode;					//单恋表的结点类型

2. 创建新结点

  • 开辟一个结点空间,将传过来的数据 x 放到结点的数据域,然后将该结点的指针域置 NULL。
SLNode* BuySListNode(SLTDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));	//开辟一个新结点空间

	if (NULL == newnode)	//如果开辟该结点失败
	{
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;		//参数给新结点的数据域
	newnode->next = NULL;	//将新结点的指针域置空

	return newnode;			//返回开辟的新结点的地址
}

3. 单链表打印

1. 遍历链表

因为不能动指向首结点的指针,防止找不到首结点,所以此类操作都是利用额外的指针来实现的。

  • 使用一个指针 cur (current) 指向当前结点,如果 cur 不为空,则说明链表还没走到头。打印 cur 所指向的结点的数据域,然后让 cur 指针指向下一个结点。
  • 在打印的时候会有两种情况。
  1. 单链表为空:此时 cur 直接就指向 NULL,什么也不会打印。

在这里插入图片描述

  1. 单链表非空:打印 cur 指向结点的数据域的类容,然后将 cur 指向指向下一个结点,直到 cur 走到链表末尾后为止。

在这里插入图片描述

2. 代码实现

 //单链表打印
void SListPrint(SLNode* plist)
{
	SLNode* cur = plist;			//指向单链表的首结点

	while (cur != NULL)				//单链表还没走到尾
	{
		printf("%d->", cur->data);	//打印当前结点数据域的值
		cur = cur->next;			//继续指向下一个结点
	}
	printf("NULL\n");
}

3. 函数调用

SListPrint(plist);

4. 单链表尾插

1. 插入链表

将数据依次插入到单链表的尾部。
在进行单链表尾插时有两种情况:

  1. 单链表为空:直接创建一个新结点然后插入到单链表即可。

在这里插入图片描述

  1. 单链表非空:使用一个 tail 指针用来寻找单链表的尾部,要将新结点插入到 tail->next 为 NULL 的位置。

在这里插入图片描述

2. 代码实现

//传过来的是个指针变量的地址,需要使用二级指针 pplist 来接收
void SListPushBack(SLNode** pplist, SLTDataType x)
{
	assert(pplist);

	SLNode* newnode = BuySListNode(x);	//创建一个新的结点

	if (NULL == *pplist)				//单链表当前为空
	{
		*pplist= newnode;				//直接将新结点插入链表
	}
	else								//单链表非空
	{
		SLNode* tail = *pplist;			//tail 用于寻找单链表的尾结点

		while (tail->next != NULL)		//没有找到尾结点
		{
			tail = tail->next;			//指向下一个结点
		}
		tail->next = newnode;			//将新结点插入到尾结点之后,成为新的尾结点
	}
}

3. 函数调用

在这里插入图片描述

5. 单链表头插

1. 插入链表

  • 不管链表中有多少个结点,所有的数据都插入到链表的都一个位置

在这里插入图片描述

2. 代码实现

//要改变链表内容,实参要传指针变量的地址,形参用二级指针接收
void SListPushFront(SLNode** pplist, SLTDataType x)
{
	assert(pplist);						//传过来的不能是 NULL 指针

	SLNode* newnode = BuySListNode(x);	//创建新结点
	newnode->next = *pplist;			//将首结点地址交给新结点指针域
	*pplist= newnode;					//让新结点成为新的首结点
}

3. 调用函数

在这里插入图片描述

6. 单链表尾删

  • 每次将单链表尾部的的一个结点删除掉。

1. 实现方法

  1. 链表中只一个结点:直接释放链表指针指向的那个结点。

在这里插入图片描述

  1. 链表中有多个结点:使用一个 tail 指针寻找尾结点的前一个结点,如果 tail 所指向结点的下个结点的指针域为空,则说明 tail->next 指向的结点为尾结点,直接释放即可。

在这里插入图片描述

2. 实现代码

void SListPopBack(SLNode** pplist)
{
	assert(pplist);							//pplist 指向的 plist 不是 NULL
	assert(*pplist);						//plist 指向的链表不为空

	if (NULL == (*pplist)->next)			//1.一个结点
	{
		free(*pplist);						//释放链表中唯一的一个结点
		*pplist= NULL;
	}
	else									//2.多个结点
	{
		SLNode* tail = *pplist;				//使用 tail 找尾结点

		while (tail->next->next != NULL)	//当前结点的下下个结点不为空
		{
			tail = tail->next;				//tail 指向链表下一个结点
		}

		free(tail->next);					//释放 tail->next 指向的尾结点
		tail->next = NULL;					//将 tail->next 的值置空
	}
}

3. 函数调用

在这里插入图片描述

7. 单链表头删

  • 每次删除都只删除单链表的首结点。

1. 实现方法

  1. 链表有多个结点:先用一个临时指针变量保存首结点的地址,再让链表指针指向第二个结点,最后释放首结点。

在这里插入图片描述

  1. 链表只一个结点:步骤和上面相同,只不过只有一个结点时,链表指针在第二步会指向 NULL 而已。

2. 实现代码

void SListPopFront(SLNode** pplist)
{
	assert(pplist);				//传过来的不是 NULL
	assert(*pplist);			//链表不为空

	SLNode* tmp = *pplist;		//先保存链表首结点
	*pplist= (*pplist)->next;	//让链表指针指向第二个结点
	free(tmp);					//释放首结点
}

3. 函数调用

在这里插入图片描述

8. 单链表查找

  • 在单链表中查找数据域的值等于形参 x 的结点,并返回该结点的地址。
  • 如果链表中不存在 x 则返回 NULL。

实现代码

SLNode* SListFind(SLNode* plist, SLTDataType x)
{
	assert(plist);				//链表不为空

	SLNode* cur = plist;		//cur 用来访问当前结点

	while (cur != NULL)			//cur 没有走到链表末尾
	{
		if (cur->data == x)		//当前结点数据域的值等于形参 x
		{
			return cur;			//返回当前结点的地址
		}
		else					
		{
			cur = cur->next;	//继续往后找数据域等于 x 的结点
		}
	}

	return cur;					//链表中没有 x,返回 NULL
}

调用函数

在这里插入图片描述

9. 在指定 pos 位置前插入结点

1. 实现方法

  • 定义一个 cur (current) 指针指向当前结点,如果当前结点的下一个结点的地址等于 pos 的地址,则将 pos 指向的结点插入到新结点后,再将新结点插入到 cur 结点后。

在这里插入图片描述

2. 实现代码

void SLTInsert(SLNode** pplist, SLNode* pos, SLTDataType x)
{
	assert(pplist);						//传过来的不能是空指针
	assert((*pplist) && (pos));			//要么同时不为空
	assert(!(*pplist) && !(pos));		//要么同时为空

	SLNode* newnode = BuySListNode(x);	//创建新结点

	if (*pplist == pos)					//链表只有一个结点 - 头插
	{
		SListPopFront(pplist,x)			//新结点作首结点
	}
	else								//找 pos 位置的前一个
	{
		SLNode* cur = *pplist;			//cur 是 pos 位置的前一个结点

		while (cur->next != pos)		//当前结点的下个结点不是 pos 
		{
			cur = cur->next;			//cur 向后寻找 pos 结点
		}

		newnode->next = pos;		//让新结点指针域指向 pos 结点
		cur->next = newnode;			//当前结点的指针域指向新结点
	}
}

调用函数

在这里插入图片描述

10. 删除指定 pos 位置的结点

1. 实现思路

  1. 在删除 pos 位置的结点前,先用一个前趋指针 pre 保存 pos 结点的前一个结点。
  2. 让 pre 指向 pos 的下一个结点。
  3. 销毁 pos 指向的结点。

在这里插入图片描述

2. 实现代码

void SLTErase(SLNode** pplist, SLNode* pos)
{
	assert(pplist);
	assert((*pplist) && (pos));		//两个指针同时不为空

	SLNode* pre = NULL;				//当前位置的前一个结点
	SLNode* cur = *pplist;			//用来寻找 pos 结点

	while (cur != NULL)
	{
		if (cur != pos)			
		{
			pre = cur;
			cur = cur->next;
		}
		else						//cur 找到了 pos 指向的结点
		{
			pre->next = cur->next;	//pos 的前一个结点指向 pos 的后一个结点
			free(cur);				//删除 pos 指向的结点
			cur = NULL;
		}
	}
}

11. 单链表销毁

  • 对单链表连续执行头删。

实现代码

void SLTDestroy(SLNode** pplist)
{
	assert(pplist);
	
	SLNode* cur = *pplist;

	while (cur)						//链表不为空则执行头删
	{
		SLNode* next = cur->next;	//保存当前结点的下一个结点
		free(cur);					//释放当前结点
		cur = next;					//对下一个结点执行以上操作
	}
	
	*pplist = NULL;
}

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

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

相关文章

阿里云服务器优惠购买和搭建网站全流程(图文教程)

阿里云服务器使用教程包括云服务器购买、云服务器配置选择、云服务器开通端口号、搭建网站所需Web环境、安装网站程序、域名解析到云服务器公网IP地址,最后网站上线全流程,新手站长xinshouzhanzhang.com分享阿里云服务器详细使用教程: 一&am…

win11系统完全卸载Oracle11g图文详细步骤

完全卸载Oracle11g图文详细步骤 卸载步骤: 1.停用Oracle服务 2.卸载Oracle产品 3.删除注册表 4.删除环境变量 5.删除安装文件 6.重启电脑 文章目录 1. 停用Oracle服务2. 卸载Oracle产品3. 删除注册表4. 删除环境变量5. 删除安装文件6. 重启电脑扩展了解一下 Oracle相…

Requests 与接口请求构造

Requests 是一个优雅而简单的 Python HTTP 库,其实 Python 内置了用于访问网络的资源模块,比如urllib,但是它远不如 Requests 简单优雅,而且缺少了许多实用功能。所以,更推荐掌握 Requests 接口测试实战技能&#xff0…

oracle_19c 安装

oracle安装部署 1、安装docker,docker-compose环境。 curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun curl -L "https://github.com/docker/compose/releases/download/1.14.0-rc2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/b…

springboot初始化

一、 SpringBean 1. Spring Bean 1) Bean定义 Bean是什么,Bean是特殊的对象,交由Spring管理的Java对象,这类对象在创建的时候会根据spring的一些注解,和IOC,属性如果使用Autowired的话,会自动赋值。Bean…

[AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]

场景 在使用Android Studio的虚拟设备运行App时,需要创建很大镜像文件。这些镜像文件一般都在系统盘,导致系统盘占用增大。怎么把这些镜像的存放路径设置在其他盘? 说明 虚拟设备的和它的镜像默认是放在用户目录\.android\avd位置。如果是在…

同样是PM,产品经理和项目经理有啥不一样?

大家好,我是老原。身边有很多人都问: “干几年的技术可以做到项目经理?” “我要从项目经理转型到产品经理吗?” “产品经理和项目经理,哪个发展前(钱)景更好” …… 不难发现,…

Python中日志异步发送到远程服务器

背景 在Python中使用日志最常用的方式就是在控制台和文件中输出日志了,logging模块也很好的提供的相应 的类,使用起来也非常方便,但是有时我们可能会有一些需求,如还需要将日志发送到远端,或者直接写入数 据库,这种需求该如何实现呢? StreamHandler和FileHandler # -*- cod…

java泛型的深入 泛型还可以在很多地方进行定义 泛型类 泛型方法 泛型接口 泛型的继承和通配符 泛型类练习

文章目录 泛型的深入泛型还可以在很多地方进行定义泛型类泛型方法泛型接口 泛型的继承和通配符泛型类练习总结 泛型的深入 public static void main(String[] args) {//在没有泛型的时候怎么存储数据ArrayList listnew ArrayList();list.add(1);list.add("abc");//遍…

C语言 用字符串比较函数cmp来做一个门禁:账号密码是否匹配 (干货满满)

#include<stdio.h> #include<string.h> void fun04() {for (int i 0; i < 3; i){char *str01 "hello";char uname[100] ;printf("请输入账号");scanf("%s",uname);char *str02 "123456";char pword[100];printf(&qu…

数字化转型:云表低代码开发助力制造业腾飞

数字化转型已成为制造业不可避免的趋势。为了应对市场快速变化、提高运营效率以及降低成本&#xff0c;制造业企业积极追求更加智能化、敏捷的生产方式。在这个转型过程中&#xff0c;低代码技术作为一种强大的工具&#xff0c;正逐渐崭露头角&#xff0c;有望加速制造业的数字…

QGC 中添加海康威视摄像头记录(Qt For Android 使用 JNI 进行JAVA 与 C++ 的通讯)

文章目录 1. 配置海康威视 SDK 下载库文件移植工程文件添加动态库&#xff08;.so&#xff09;Android xml 配置添加 java 文件 2. JavaQGCActivity.javaHkwsManager.java 3. C头文件添加&#xff1a;C 中调用 Java 静态函数&#xff08;hcnNetSDKInit&#xff09;JNI 传入规则…

【电路笔记】-串联RLC电路分析

串联RLC电路分析 文章目录 串联RLC电路分析1、概述2、瞬态响应3、AC响应4、RCL和CLR配置5、结论 电阻器 、电感器 (L) 和电容器 © 是电子器件中的三个基本无源元件。 它们的属性和行为已在交流电阻、交流电感和交流电容文章中详细介绍。 在本文中&#xff0c;我们将重点讨…

二蛋赠书七期:《云原生数据中台:架构、方法论与实践》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

基于北方苍鹰算法的无人机航迹规划-附代码

基于北方苍鹰算法的无人机航迹规划 文章目录 基于北方苍鹰算法的无人机航迹规划1.北方苍鹰搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用北方苍鹰算法来优化无人机航迹规划。 …

Qt QTableView排序

1.简介 在开发过程中&#xff0c;我们需要通过点击表头来对QTableView或QTreeView等一系列高级视图进行排序操作&#xff0c;以下是进行排序的步骤。 步骤&#xff1a; 首先创建了一个QStandardItemModel对象或者继承QAbstractTableModel类作为数据模型&#xff0c;并设置了…

如何获取HuggingFace的Access Token;如何获取HuggingFace的API Key

Access Token通过编程方式向 HuggingFace 验证您的身份&#xff0c;允许应用程序执行由授予的权限范围&#xff08;读取、写入或管理&#xff09;指定的特定操作。您可以通过以下步骤获取&#xff1a; 1.首先&#xff0c;你需要注册一个 Hugging Face 账号。如果你已经有了账号…

Android修行手册 - 实现POI上万行的大数据量Excel读写操作,解决内存溢出

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

web前端JS基础------制作一个获取验证码

1&#xff0c;需要一个定时器&#xff0c;和一个button&#xff0c;通过点击事件启动获取验证码 2&#xff0c;参考代码如下 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><…

Linux中for循环

for do done 复习知识点&#xff1a;cut命令&#xff0c;id命令&#xff0c;finger命令&#xff0c;for循环 程序如上&#xff0c;-d 接分隔符&#xff0c;-f后的数字表示分隔后的列 从结果可以看出&#xff0c;系统上没有finger这个命令&#xff0c;后面会学到yum安装命令&a…