数据结构——单链表的基本操作

news2024/10/17 5:50:41

前言

介绍

 🍃数据结构专区:数据结构

参考

该部分知识参考于《数据结构(C语言版 第2版)》29~36页

补充

后序代码中会遇见这个结构体

typedef struct LNode
{
...
}LNode,*LinkList;

 对于这个代码,目的是定义线性表的单链表储存结构

关键在于LNode与*LinkList

抽象出两个句子:
typedef struct Node LNode;
typedef struct Node* LinkList;

LNode,参照typede的用法,可以得知LNode就是struct LNode的别名,即LNode==struct LNode;
LinkList,是一个指向该结构体的的指针的别名。其实这个*应该不是跟着LinkList,而是跟在LNode后面的,即LNode* == LinkList。
可以通过这样一个例子可以这样来理解
typedef struct int ElemType
typedef struct int* ElemTypePtr
第一个是 定义整型变量的别名 ElemType
第二个是 定义指向整型变量的指针的别名 ElemTypePtr

LNode Node;//定义结构体变量Node;
LinkList Ptr;//定义指向结构体的指针变量Ptr;

🌈每一个清晨,都是世界对你说的最温柔的早安:ૢ(≧▽≦)و✨


目录

前言

1、单链表的基本概念

2、单链表的基本操作

2.1 宏定义和结构体

2.2 初始化  O(1)

2.3 销毁  O(n)

2.4 判空  O(1)

2.5 求表长  O(n)

2.6 取值(按序号查找)  O(n)

2.7 按值查找  O(n)

2.8 指定插入  O(n)

2.9 删除  O(n)

2.10 输出链表

2.11 头插法  O(n)

2.12 尾插法  O(n)

2.13 整体代码(含测试)

结语


1、单链表的基本概念

单链表(Singly Linked List)是一种常见的数据结构,用于存储一系列的元素,其中每个元素称为节点(Node)。在单链表中,每个节点包含两部分:一部分存储数据(data),另一部分存储指向下一个节点的指针(pointer 或 next)。以下是单链表的一些基本概念:

  1. 节点(Node)
    • 节点是单链表的基本单元。
    • 每个节点包含两个部分:
      • 数据域(data):存储实际的数据。
      • 指针域(next):存储指向下一个节点的指针(如果当前节点是最后一个节点,则指针为 null 或 NULL)。
  2. 头节点(Head Node)
    • 单链表通常有一个头节点,它指向链表的第一个实际数据节点(如果链表不为空)。
    • 头节点本身不存储数据,也可以存储一个哨兵值(dummy value)或者作为链表操作的辅助。
  3. 尾节点(Tail Node)
    • 链表的最后一个节点,其 next 指针为 null 或 NULL。
  4. 链表长度(Length)
    • 链表中节点的数量。
    • 通常通过一个额外的变量来维护链表的长度,以加快获取链表长度的操作。
  5. 操作
    • 插入(Insert):在链表的特定位置插入一个新节点。
    • 删除(Delete):删除链表中的某个节点。
    • 查找(Search):在链表中查找特定值的节点。
    • 遍历(Traversal):从头节点开始,依次访问链表中的每个节点。
  6. 时间复杂度
    • 插入、删除和查找操作的时间复杂度通常为 O(n),其中 n 是链表的长度。这是因为这些操作在最坏情况下需要遍历链表的一部分或全部节点。
    • 遍历链表的时间复杂度为 O(n)。
  7. 内存管理
    • 单链表使用动态内存分配来创建节点,因此在使用完毕后需要手动释放每个节点的内存,以避免内存泄漏。

单链表相比于数组,其优势在于插入和删除操作的时间复杂度较低(在已知位置的情况下),但劣势在于访问特定位置的节点需要从头节点开始遍历,不如数组那样高效。 

2、单链表的基本操作

2.1 宏定义和结构体

#include<iostream>
using namespace std;

//控制最大值
#define MAXSIZE 1000
//声明Status用于记录返回结果
typedef int Status;
#define OK 1
#define ERROR 0
#define OVERFLOW -1

typedef int ElemType;
typedef struct LNode
{
	ElemType data;
	struct LNode* next;
}LNode, *LinkList;

2.2 初始化  O(1)

//初始化 O(1)
Status InitList(LinkList& L)
{
	L = new LNode;
	//等价于
	//L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;
	return OK;
}

2.3 销毁  O(n)

//链表销毁 O(n),n为链表中数据节点的个数
Status DestroyLink(LinkList& L)
{
	LNode* pre = L;
	LNode* p = L->next;  //pre指向节点p的前驱节点
	while (p)
	{
		free(pre);  //释放pre节点
		pre = p;    //pre , p 同步向后移动一位
		p = pre->next;
	}
	//循环结束后,p为NULL,pre指向尾节点,释放它
	free(pre);
	return OK;
}

2.4 判空  O(1)

//判空 O(1)
Status ListEmpty(LinkList L)
{
	return(L->next == NULL);
}

2.5 求表长  O(n)

//求表长  O(n),n为链表中数据节点的个数
int ListLength(LinkList L)
{
	int n = 0;
	LNode* p = L->next;
	while (p)
	{
		n++;
		p = p->next;
	}
	return n;
}

2.6 取值(按序号查找)  O(n)

//取值  O(n),n为链表中数据节点的个数
//按照序号进行查找
Status GetElemLink(LinkList L, int i, ElemType& e)
{
	LNode* p = L->next;
	int j = 1;
	while (p && j < i)  //顺链表域向后查找,直到p为空或p指向第i个元素
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)  //i值不合法
	{
		return ERROR;  
	}
	else
	{
		e = p->data;  //用引用返回得到的参数数据
		return OK;
	}
}

2.7 按值查找  O(n)

//按照元素进行查找  O(n),n为链表中数据节点的个数
LNode* LocateElem(LinkList L, ElemType e)
{
	LNode* p;
	p = L->next;
	while (p && p->data != e)  //顺链表域向后查找,直到p为空或者p所指节点的数据域为e
	{
		p = p->next;
	}
	return p;   //查找到后返回e元素的结点地址p,查找失败后返回为NULL
}

2.8 指定插入  O(n)

//链表插入  O(n),n为链表中数据节点的个数
//将e节点元素插入到i节点的前面,则需要移动到i-1的位置
Status InsertLink(LinkList& L, int i, ElemType e)
{
	LNode* p = L;
	int j = 0;
	while (p && j < i - 1)  //查找到第i-1个节点
	{
		++j;
		p = p->next;
	}
	if (!p || j > i - 1)
	{
		return ERROR;
	}//该情况出现,即插入位置非法
	//循环结束后,已经移动到了i节点的前一个位置,进行尾插即可
	LNode* s = new LNode; //为即将要插入位置开辟一个结点
	s->data = e;
	s->next = p->next;//首先进行尾部连接
	p->next = s;//随后进行头部连接
	return OK;
}

 

2.9 删除  O(n)

//链表删除某个结点  O(n),n为链表中数据节点的个数
Status DeleteLink(LinkList& L, int i, ElemType e)
{
	LNode* p = L;
	int j = 0;
	while (p && j < i - 1)
	{
		p = p->next;
		j++;
	}//循环结束后,p会指向要删除节点的前驱节点
	if (!(p->next) || j > i - 1)
	{
		return ERROR;
	}//要判断位置是否非法
	//进行删除
	LNode* q;
	q = p->next;    //临时保存被删除节点的地址以备后续释放
	p->next = q->next;  //改变被删除节点的前驱节点的指针域
	e = q->data;  //删除时拿出来被删除节点的数据
	delete q;
	return OK;
}

 

2.10 输出链表

//遍历打印链表  O(n),n为链表中数据节点的个数
void TraverseList(LinkList L)
{
	if (L == nullptr || L->next == nullptr) {
		cout << "链表为空" << endl;
		return;
	}
	LNode* p;
	p = L->next;
	while (p)
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
}

2.11 头插法  O(n)

//头插法  O(n),n为链表中数据节点的个数
void CreatLink_F(LinkList& L, int n)
{
	L = new LNode;
	L->next = NULL;
	for (int i = 0; i < n; i++)
	{
		LinkList p = new LNode;
		cin >> p->data;
		p->next = L->next;
		L->next = p;
	}
}

2.12 尾插法  O(n)

//尾插法  O(n),n为链表中数据节点的个数
void CreatLink_L(LinkList& L, int n)
{
	L = new LNode;
	L->next = NULL;
	LNode* r = L;
	for (int i = 0; i < n; i++)
	{
		LNode* p = new LNode;
		cin >> p->data;
		p->next = NULL;
		r->next = p;
	}
}

2.13 整体代码(含测试)

#include<iostream>
using namespace std;

//控制最大值
#define MAXSIZE 1000
//声明Status用于记录返回结果
typedef int Status;
#define OK 1
#define ERROR 0
#define OVERFLOW -1

typedef int ElemType;
typedef struct LNode
{
	ElemType data;
	struct LNode* next;
}LNode, *LinkList;

//初始化 O(1)
Status InitList(LinkList& L)
{
	L = new LNode;
	//等价于
	//L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;
	return OK;
}

//链表销毁 O(n),n为链表中数据节点的个数
Status DestroyLink(LinkList& L)
{
	LNode* pre = L;
	LNode* p = L->next;  //pre指向节点p的前驱节点
	while (p)
	{
		free(pre);  //释放pre节点
		pre = p;    //pre , p 同步向后移动一位
		p = pre->next;
	}
	//循环结束后,p为NULL,pre指向尾节点,释放它
	free(pre);
	return OK;
}

//判空 O(1)
Status ListEmpty(LinkList L)
{
	return(L->next == NULL);
}

//求表长  O(n),n为链表中数据节点的个数
int ListLength(LinkList L)
{
	int n = 0;
	LNode* p = L->next;
	while (p)
	{
		n++;
		p = p->next;
	}
	return n;
}

//取值  O(n),n为链表中数据节点的个数
//按照序号进行查找
Status GetElemLink(LinkList L, int i, ElemType& e)
{
	LNode* p = L->next;
	int j = 1;
	while (p && j < i)  //顺链表域向后查找,直到p为空或p指向第i个元素
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)  //i值不合法
	{
		return ERROR;  
	}
	else
	{
		e = p->data;  //用引用返回得到的参数数据
		return OK;
	}
}

//按照元素进行查找  O(n),n为链表中数据节点的个数
LNode* LocateElem(LinkList L, ElemType e)
{
	LinkList p;
	p = L->next;
	while (p && p->data != e)  //顺链表域向后查找,直到p为空或者p所指节点的数据域为e
	{
		p = p->next;
	}
	return p;   //查找到后返回e元素的结点地址p,查找失败后返回为NULL
}

//链表插入  O(n),n为链表中数据节点的个数
//将e节点元素插入到i节点的前面,则需要移动到i-1的位置
Status InsertLink(LinkList& L, int i, ElemType e)
{
	LNode* p = L;
	int j = 0;
	while (p && j < i - 1)  //查找到第i-1个节点
	{
		++j;
		p = p->next;
	}
	if (!p && j > i - 1)
	{
		return ERROR;
	}//该情况出现,即插入位置非法
	//循环结束后,已经移动到了i节点的前一个位置,进行尾插即可
	LNode* s = new LNode; //为即将要插入位置开辟一个结点
	s->data = e;
	s->next = p->next;//首先进行尾部连接
	p->next = s;//随后进行头部连接
	return OK;
}

//链表删除某个结点  O(n),n为链表中数据节点的个数
Status DeleteLink(LinkList& L, int i, ElemType e)
{
	LNode* p = L;
	int j = 0;
	while (p && j < i - 1)
	{
		p = p->next;
		j++;
	}//循环结束后,p会指向要删除节点的前驱节点
	if (!(p->next) || j > i - 1)
	{
		return ERROR;
	}//要判断位置是否非法
	//进行删除
	LNode* q;
	q = p->next;    //临时保存被删除节点的地址以备后续释放
	p->next = q->next;  //改变被删除节点的前驱节点的指针域
	e = q->data;  //删除时拿出来被删除节点的数据
	delete q;
	return OK;
}

//遍历打印链表  O(n),n为链表中数据节点的个数
void TraverseList(LinkList L)
{
	if (L == nullptr || L->next == nullptr) {
		cout << "链表为空" << endl;
		return;
	}
	LNode* p;
	p = L->next;
	while (p)
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
}

//头插法  O(n),n为链表中数据节点的个数
void CreatLink_F(LinkList& L, int n)
{
	L = new LNode;
	L->next = NULL;
	for (int i = 0; i < n; i++)
	{
		LinkList p = new LNode;
		cin >> p->data;
		p->next = L->next;
		L->next = p;
	}
}

//尾插法  O(n),n为链表中数据节点的个数
void CreatLink_L(LinkList& L, int n)
{
	L = new LNode;
	L->next = NULL;
	LNode* r = L;
	for (int i = 0; i < n; i++)
	{
		LNode* p = new LNode;
		cin >> p->data;
		p->next = NULL;
		r->next = p;
	}
}

int main() {
	LinkList L;
	ElemType e;

	cout << "开始测试单链表操作函数:" << endl;

	// 测试初始化函数
	cout << "\n1. 测试初始化函数 InitList" << endl;
	if (InitList(L) == OK) {
		cout << "链表初始化成功" << endl;
	}
	else {
		cout << "链表初始化失败" << endl;
		return 1;
	}

	// 测试插入函数
	cout << "\n2. 测试插入函数 InsertLink" << endl;
	for (int i = 1; i <= 5; i++) {
		if (InsertLink(L, i, i * 10) == OK) {
			cout << "成功在位置 " << i << " 插入元素 " << i * 10 << endl;
		}
		else {
			cout << "在位置 " << i << " 插入元素失败" << endl;
		}
	}

	// 测试遍历打印函数
	cout << "\n3. 测试遍历打印函数 TraverseList" << endl;
	cout << "当前链表内容:";
	TraverseList(L);

	// 测试取值函数
	cout << "\n4. 测试取值函数 GetElemLink" << endl;
	if (GetElemLink(L, 3, e) == OK) {
		cout << "第3个元素的值为:" << e << endl;
	}
	else {
		cout << "获取第3个元素失败" << endl;
	}

	// 测试查找函数
	cout << "\n5. 测试查找函数 LocateElem" << endl;
	e = 30;
	LNode* found = LocateElem(L, e);
	if (found) {
		cout << "元素 " << e << " 在链表中" << endl;
	}
	else {
		cout << "元素 " << e << " 不在链表中" << endl;
	}

	// 测试删除函数
	cout << "\n6. 测试删除函数 DeleteLink" << endl;
	if (DeleteLink(L, 2, e) == OK) {
		cout << "成功删除第2个元素,删除的元素值为:" << e << endl;
		cout << "删除后的链表内容:";
		TraverseList(L);
	}
	else {
		cout << "删除第2个元素失败" << endl;
	}

	// 测试求长度函数
	cout << "\n7. 测试求长度函数 ListLength" << endl;
	cout << "当前链表的长度为:" << ListLength(L) << endl;

	// 测试判空函数
	cout << "\n8. 测试判空函数 ListEmpty" << endl;
	if (ListEmpty(L) == OK) {
		cout << "链表为空" << endl;
	}
	else {
		cout << "链表不为空" << endl;
	}

	// 测试头插法创建链表
	cout << "\n9. 测试头插法创建链表 CreatLink_F" << endl;
	LinkList L1;
	cout << "请输入3个元素用于头插法创建链表:";
	CreatLink_F(L1, 3);
	cout << "头插法创建的链表内容:";
	TraverseList(L1);

	// 测试尾插法创建链表
	cout << "\n10. 测试尾插法创建链表 CreatLink_L" << endl;
	LinkList L2;
	cout << "请输入3个元素用于尾插法创建链表:";
	CreatLink_L(L2, 3);
	cout << "尾插法创建的链表内容:";
	TraverseList(L2);

	// 测试销毁函数
	cout << "\n11. 测试销毁函数 DestroyLink" << endl;
	if (DestroyLink(L) == OK && DestroyLink(L1) == OK && DestroyLink(L2) == OK) {
		cout << "链表销毁成功" << endl;
	}
	else {
		cout << "链表销毁失败" << endl;
	}

	cout << "\n所有测试完成" << endl;

	return 0;
}

结语

该部分内容需要大家实操才可以有所收获,同时这部分又是后续学习中的基础,希望大家可以认真对待! 

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

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

相关文章

【云原生kubernetes系列--coredns篇】

1.corednsd的介绍 官网&#xff1a;https://coredns.io/ CoreDNS是一个灵活、可扩展的 DNS 服务器&#xff0c;可以充当 Kubernetes 集群 DNS。与 Kubernetes 一样&#xff0c;CoreDNS 项目由 CNCF coredns在K8S中的用途,主要是用作服务发现&#xff0c;也就是服务(应用)之间…

Python中 文件操作及与数据库的交互

在数据驱动的时代&#xff0c;Python不仅是一门强大的编程语言&#xff0c;更是与文件系统和数据库交互的重要工具。无论是读取配置文件、处理数据集&#xff0c;还是与数据库进行交互&#xff0c;Python都能轻松胜任。那么&#xff0c;如何高效地进行文件操作&#xff0c;并实…

uniapp 小程序0到1教程

先说明一下&#xff0c;uni-app的文档很乱 一、注册微信小程序 如果你还没有微信公众平台的账号&#xff0c;请先进入微信公众平台首页&#xff0c;点击 “立即注册” 按钮进行注册。注册的账号类型可以是订阅号、服务号、小程序以及企业微信&#xff0c;我们选择 “小程序”…

基本计算器 II

文章目录 题目解析解题小结 题目解析 给你一个字符串表达式 s &#xff0c;请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。 你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。 注意&#xff1a;不允许使用任何将字符…

lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 > 路由组件代码会被分开打包 const Login lazy(()>import(/pages/Login)) //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面 <Suspense fallback{<h1&…

Arduino配置ESP32环境

Arduino配置ESP32环境 引言一、IDE下载教程操作取巧方法 二、社区安装包三、官方手动安装 引言 最近入手了一款ESP32-C3的开发板&#xff0c;想继续沿用现有Arduino IDE&#xff0c;网上看了很多方法&#xff0c;大致分了三类&#xff1a;IDE下载、社区安装包、github手动配置…

法规标准-懂车帝智能化实测标准(2024版)

场景&#xff1a;AEB追尾静态假车 1.场地布置&#xff1a; ——测试选取封闭场地&#xff0c;试验路面应为水平、干燥&#xff0c;具有良好附着能力的混凝土沥青路面&#xff0c;附着系数在0.8以上 ——试验过程中&#xff0c;在试验道路两边3m以内或者静止目标车前方30m内不能…

简单掌握 Android Studio 模拟器

下载 Android Studio安装adb、配置 adb创建一个新的Activity项目创建模拟器 参考&#xff1a;mac系统下android studio创建手机模拟器adb命令使用&#xff0c;可在模拟器上安装app 打开终端 adb devices // 查询设备 adb install xx/xx/xx // 安装apk&#xff08;apk路径拖进…

Learning to Adapt to Light

Abstract 光适应或亮度校正是提高图像对比度和视觉吸引力的关键步骤。 有多种与光相关的任务&#xff08;例如&#xff0c;低光增强和曝光校正&#xff09;&#xff0c;之前的研究主要单独研究这些任务。 然而&#xff0c;考虑这些与光相关的任务是否可以通过统一的模型来执行…

扫雷(C 语言)

目录 一、游戏设计分析二、各个步骤的代码实现1. 游戏菜单界面的实现2. 游戏初始化3. 开始扫雷 三、完整代码四、总结 一、游戏设计分析 本次设计的扫雷游戏是展示一个 9 * 9 的棋盘&#xff0c;然后输入坐标进行判断&#xff0c;若是雷&#xff0c;则游戏结束&#xff0c;否则…

南京邮电大学电工电子A实验九译码器及其应用

文章目录 1 实验报告预览2 Word版本报告下载 1 实验报告预览 2 Word版本报告下载 点我

git push错误failed to push some refs to解决方法

主流解决方法网上全是&#xff0c;例如解决目标仓库和本地仓库的版本冲突&#xff1b;关闭”受保护的仓库“权限。 本文讲述一种 网上几乎没有文章会讲解的一种可能的解决方式&#xff1a; 问题描述&#xff1a; 解决方式&#xff1a; 取消勾选即可

【Cadence27】HDL拷贝工程➕Allegro导出DXF和3D文件STP

【转载】Cadence Design Entry HDL 使用教程 【Cadence01】Cadence PCB Edit相对延迟与绝对延迟的显示问题 【Cadence02】Allegro引脚焊盘Pin设置为透明 【Cadence03】cadence不小心删掉钢网层怎么办&#xff1f; 【Cadence04】一般情况下Allegro PCB设计时的约束规则设置&a…

python之socket网络编程

华子目录 引言什么是socketsocket套接字类型TCP和UDP socket服务端核心组件1.创建socket对象2.绑定地址和端口3.监听连接4.接受连接5.接受client端消息client_sock.revc(1024)6.发送响应给client端6.1client_sock.send()6.2client_sock.sendall() 7.关闭client端连接8.关闭serv…

【初阶数据结构】归并排序 - 分而治之的排序魔法

文章目录 前言1. 什么是归并排序&#xff1f;1.1 归并排序的步骤 2. 归并排序的代码实现2.1 归并排序代码的关键部分讲解2.1.1 利用递归2.1.2 将拆解的数组的元素放到一个临时空间中进行重新排序2.1.3 将在临时空间中排好的数组复制到目标数组中 3. 归并排序的非递归写法 前言 …

未来汽车究竟该是什么样子?

24年10月14日&#xff0c;在中国&#xff08;深圳&#xff09;机器视觉展暨机器视觉技术及工业应用研讨会上&#xff0c;同行者分享了未来智能座舱应该长什么样子。 受此启发&#xff0c;个人觉得当前大多数新能源车都极力想做出电动感&#xff0c;但是布局传统没跳出来&#…

DAY52WEB 攻防-XSS 跨站反射型存储型DOM 型标签闭合输入输出JS 代码解析

#知识点&#xff1a; 1、XSS跨站-输入输出-原理&分类&闭合 2、XSS跨站-分类测试-反射&存储&DOM 演示案例&#xff1a; ➢XSS跨站-输入输出-原理&分类&闭合 ➢XSS跨站-分类测试-反射&存储&DOM #XSS跨站-输入输出-原理&分类&闭合 漏…

安防监控摄像头图传模组,1公里WiFi无线传输方案,监控新科技

在数字化浪潮汹涌的今天&#xff0c;安防监控领域也迎来了技术革新的春风。今天&#xff0c;我们就来聊聊这一领域的产品——摄像头图传模组&#xff0c;以及它如何借助飞睿智能1公里WiFi无线传输技术&#xff0c;为安防监控带来未有的便利与高效。 一、安防监控的新篇章 随着…

基于Java+Springboot+Vue开发的酒店客房预订管理系统

项目简介 该项目是基于JavaSpringbootVue开发的酒店客房预订管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Java…

【AI学习】Mamba学习(七):HiPPO通用框架介绍

HiPPO这篇论文《HiPPO: Recurrent Memory with Optimal Polynomial Projections》&#xff0c;提出了一个通用框架。 我们再重新看一下论文的摘要&#xff1a; 从连续数据中学习的一个核心问题是&#xff0c;随着更多数据的处理&#xff0c;以增量方式表示累积历史。我们介绍了…