单链表——单链表的定义及基本操作(初始化、头插法尾插法建表、查找、插入、删除、判空等)

news2024/11/19 0:48:09

单链表的定义

由于顺序表存在以下缺陷,所以衍生出了链表,而链表种类有很多种,今天我们讲的是单链表

顺序表存在的问题如下

1.中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

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

链表就像开火车一样,火车头代表开始,后面链接着运送货物的车厢。

链表的分类:实际中链表的结构非常多样,以下情况组合起来就有8种链表结构。

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构无头单向非循环链表和带头双向循环链表。

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

今天我们重点来实现无头单向非循环链表的实现俗称单链表

单链表上的操作

 

1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定是连续的。

2.现实中的结点一般都是在堆上malloc申请出来的。

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

4.如果最后一个结点后面没有要存储的值,要把最后一个结点的指针设成NULL,方便下一次的插入新存储的值。

建立单链表

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{
	SedListdatatype data;
	struct SedList* next;
}SLNode;

因为我们不知道单链表存储的数据是什么类型的,所以我们把int重定义成 SedListdatatype,如果我们要改其他数据类型,只需要在上面改一下即可。

我们再把结构体的名字也重定义方便实现单链表的功能实现。

单链表不需要初始化,因为我们都是从堆上malloc申请出的空间结点。

数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。 

单链表的打印

void SLprint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}

单链表传过来的指针为空,就打印NULL。因为单链表传过来的指针也可能为空,所以不用断言。

这里while的条件为什么是cur!=NULL?因为这样才能打印单链表的所有存储的值,如果写出cur->next,单链表最后一个元素存储的值就不能打印出来,因为单链表最后一个结点指针为NULL

所以当cur为NULL就可以停止打印。

创建malloc出新结点

SLNode* buynode(SedListdatatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

我们要在单链表中实现尾插和头插等等功能,这个结点包括存储的数据和指针(地址)为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点

单链表的头插

void SLPushFront(SLNode** phead, SedListdatatype x)
{
	assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	newnode->next = *phead;
	*phead = newnode;
}

单链表的尾插

void SLPushBack(SLNode** phead, SedListdatatype x)
{
	assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	链表为空
	if (*phead == NULL)
	{
		*phead = newnode;
不用让newnode->next=NULL,因为我们在开堆上申请新结点的时候已经完成了这步操作

	}
	多个结点
	SLNode* cur = *phead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

 当我们遍历单链表的时候,最好给一个另外的变量赋值去遍历,因为我们有时候会需要找到头的地址,为了不丢失头指针的地址,所以我们不用头指针phead自己遍历单链表

另外大家已经注意到为什么头插和尾插使用的是二级指针?因为结构体里面的next本身就是一个一级指针,尾插和头插,都会改变结构体里面存储的数据,而修改一级指针的内容就需要去二级指针来存储一级指针的地址,并传址才能改变单链表的内容,如果使用一级指针来存储一级指针的地址出了循环以后就会销毁,并不会影响单链表的内容也不能增删查改和管理存储数据。相当于形参与实参的差别。形参不会影响实参的改变,只是实参的临时的一份拷贝。所以要用二级指针。

当尾插时链表为空,就相当于头插。不为空时,就找尾再插入即可。

单链表的头删 

void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查  选一种即可
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		*phead = (*phead)->next;
		free(cur);
	}

}

单链表的尾删

void SLPopBack(SLNode** phead)
{

	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查 选一种即可
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur=cur->next;
		}
		free(cur);
		pre->next = NULL;
	}
	


}

用一个新指针指向cur指向的上一个结点next,等cur找到最后一个NULL就free,然后再把pre的next设为NULL即可。

单链表的遍历和查找数据 

SLNode* Find(SLNode* phead, SedListdatatype x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;


}

函数的返回值不再是void而是一个指针变量,因为我们要把找到的结点地址返回回去,这个函数可以配合修改返回当前结点的值。所以这个函数既能查找又修改单链表结点。

单链表的插入分为pos之前插入和pos之后插入

单链表的pos之后插入

void SLInsertAfter(SLNode* pos, SedListdatatype x)
{
	SLNode* newnode = buynode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

单链表的删除分为pos之前删除和pos之后删除

单链表的pos之后删除

void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);

}

单链表的销毁

void SLDestory(SLNode** phead)
{
	SLNode* cur = *phead;
	while (cur != NULL)
	{
		SLNode* pre = cur->next;
		free(cur);
		cur = pre;
	}
	*phead = NULL;
}

在销毁链表的时候不能直接free,因为单链表在物理结构上是不连续存储的,必须要一个一个结点的销毁,再把phead设为NULL。

 

总代码展示

SedList.h
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{
	SedListdatatype data;
	struct SedList* next;
}SLNode;

void SLprint(SLNode* phead);//打印

void SLPushFront(SLNode** phead, SedListdatatype x);//头插
void SLPushBack(SLNode** phead, SedListdatatype x);//尾插

void SLPopFront(SLNode** phead);//头删
void SLPopBack(SLNode** phead);//尾删

SLNode* Find(SLNode* phead,SedListdatatype x);//查找和遍历

void SLInsertAfter(SLNode* pos, SedListdatatype x);//在pos之后插入
void SLEraseAfter(SLNode* pos);//在pos之后删除

void SLDestory(SLNode** phead);//销毁
SedList.c
#include"SedList.h"
void SLprint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}
SLNode* buynode(SedListdatatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}
void SLPushFront(SLNode** phead, SedListdatatype x)
{
	assert(phead);//即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	newnode->next = *phead;
	*phead = newnode;
}
void SLPushBack(SLNode** phead, SedListdatatype x)
{
	assert(phead);
	SLNode* newnode = buynode(x);
	//链表为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		*phead = (*phead)->next;
		free(cur);
	}

}
void SLPopBack(SLNode** phead)
{

	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur=cur->next;
		}
		free(cur);
		pre->next = NULL;
	}
	


}
SLNode* Find(SLNode* phead, SedListdatatype x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;


}
void SLInsertAfter(SLNode* pos, SedListdatatype x)
{
	assert(pos);
	SLNode* newnode = buynode(x);
	newnode->next = pos->next;
	pos->next = newnode;


}
void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);

}

void SLDestory(SLNode** phead)
{
	SLNode* cur = *phead;
	while (cur != NULL)
	{
		SLNode* pre = cur->next;
		free(cur);
		cur = pre;
	}
	*phead = NULL;
}
test.c
#include"SedList.h"
void test1()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);

	SLprint(plist);


}
void test2()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);

	SLprint(plist);


}
void test3()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPopFront(&plist);
	SLprint(plist);


}
void test4()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPopBack(&plist);
	SLPopBack(&plist);

	SLprint(plist);


}
void test5()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLNode* pos = Find(plist, 3);
	pos->data = 40;

	SLprint(plist);


}
void test6()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLNode* pos = Find(plist, 2);
	if (pos)
	{
		SLInsertAfter(pos, 4);
	}

	SLprint(plist);


}
void test7()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLNode* pos = Find(plist, 2);
	if (pos)
	{
		SLEraseAfter(pos);
	}
	SLprint(plist);
	


}

int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	test7();

	return 0;
}

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

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

相关文章

CPU 架构(x86/ARM)简介

CPU 架构通过指令集的方式一般可分为 复杂指令集&#xff08;CISC&#xff09; 和 精简指令集&#xff08;RISC&#xff09; 两类&#xff0c;CISC 主要是 x86 架构&#xff0c;RISC 主要是 ARM 架构&#xff0c;还有 MIPS、RISC-V、PowerPC 等架构。 本文重点介绍 x86 和 ARM…

idea中maven的几个操作按钮:clean、validate、compile...

idea中Maven生命周期指令 clean命令 清除由项目编译创建的target validate命令 验证项目是否正确&#xff0c;并且所有必要的信息均可用 compile命令 编译项目的源代码 test命令 使用合适的单元测试框架来测试编译的源代码。 这些测试不应要求将代码打包或部署 verify命令 …

智安网络|一文看懂内核平台和系统服务的联系

内核平台和系统服务是操作系统中两个非常重要的组成部分。内核平台是系统底层的核心&#xff0c;负责管理和控制计算机硬件和资源的访问。而系统服务则是在内核之上运行的程序集合&#xff0c;为操作系统提供各种功能和服务。 内核平台和系统服务的关联非常密切。系统服务必须…

Emqx的简单使用

Emqx 是一个mqtt 的服务器产品。之前activemq可以作为mqtt协议的服务器&#xff0c;但是功能相对来说比较单一。Emqx作为跟Mqtt协议相关的新一代产品&#xff0c;功能实际上更为强大。 它的功能也主要体现在可视化/认证/规则/httpApi 上面。 1.Emqx 的安装 这里采用了docker…

JMeter 计算上一个接口取值到本次接口进行四则运算赋值

项目场景&#xff1a; 公司项目需要接口关联计算进行赋值&#xff1a; 项目场景&#xff1a;A接口提取的返回值&#xff0c;在传到B接口使用时&#xff0c;需要先进行四则运算后&#xff0c;再赋值使用。 A接口提取的值 B接口需要使用计算后的值 问题描述 使用beanshell预…

必须掌握的ArrayList,LinkedList,HashMap,HashTable,Collection,Colections

人的自由并不在于可以做他想做的事&#xff0c;而在于可以不做他不想做的事。 ArrayList和linkedList的区别 Array数组是基于索引&#xff08;index&#xff09;的数据结构&#xff0c;它使用索引在数组中搜索和读取数据是很快的 Array获取数据的时间复杂度是o(1)&#xff…

南卡OE系列再添新成员,造型犀利有型,性能强劲动听!

科技的快速发展让消费者对智能互联、操作体验、设计审美、安全健康等需求越发高涨&#xff0c;蓝牙耳机也正在由功能性向舒适性方向发展。如何提高蓝牙耳机的舒适度、拥有更舒适的听歌体验&#xff0c;成为蓝牙耳机品牌和消费者共同努力的方向。 Nank南卡&#xff1a;更专业的骨…

【Linux】单机版QQ之管道中的命名管道

还记得上一篇的匿名管道吗&#xff1f; 文章目录 前言一、命名管道总结 前言 命名管道是什么呢&#xff1f; 管道应用的一个限制就是只能在具有共同祖先&#xff08;具有亲缘关系&#xff09;的进程间通信。 如果我们想在不相关的进程之间交换数据&#xff0c;可以使用FIFO文…

一百零七、MySQL数据库的数据备份与数据恢复

MySQL数据库的数据备份与恢复主要有3种方法&#xff0c;前两种都是MySQL dump命令&#xff0c;第三种则是用Navicat工具直接备份。相比而言&#xff0c;第三种方法更加简单&#xff01; 1 方法一&#xff08;MySQL dump命令&#xff09; 1.1 登录MySQL [roothurys22 ~]# mysq…

Maya云渲染如何使用,Maya云渲染流程实操!

Maya 是一款专业的 3D 软件&#xff0c;用于创建逼真的角色和大片的效果&#xff0c;Maya可以加速工作流程&#xff0c;帮助您专注于创造力并按时完成任务。也可以为角色和场景添加精美的细节&#xff0c;并提供让客户满意的优质作品。更有无数业内顶级艺术家依靠 Maya来创作更…

【Halcon】新建程序 读取图片 路径设置

文章目录 1 新建程序2 读取一张图片3 图片路径4 图片格式读取报错5 快速添加 绝对路径 1 新建程序 点击新程序图标&#xff0c;即可新建&#xff1b; 程序另存为&#xff0c;会弹出保存路径 2 读取一张图片 read_image(Image,fabrik)此时工程路径下并没有图片&#xff1b; …

SpringBoot2 集成 ELK 实现日志收集

目录 一 简介 二 ELK 各组件作用 三 ELK 各组件安装 四 Spring Boot2 集成 logstash 一 简介 ELK 即 Elasticsearch、Logstash、Kibana 组合起来可以搭建线上日志系统&#xff0c;本文主要讲解使用ELK 来收集 SpringBoot2 应用产生的日志。 二 ELK 各组件作用 Elasticsea…

基于FPGA和Matlab实现的FFT功能验证

一 、FFT设计验证思路 1、基于Matlab与FPGA的混频sin信号的FFT验证&#xff0c;分别在Matlab和FPGA开发环境上实现相同的FFT功能设计。 2、Matlab平台开发&#xff0c;使用自带的fft函数与相关操作函数&#xff0c;绘制出混频sin信号&#xff0c;经过fft功能处理后的频谱图。 3…

2022 ios APP最新开发测试教程

转载&#xff1a;2022 ios APP最新开发测试教程1.本文详细介绍最新的在windows上进行ios app开发编译打包安装到手机测试的完整流程。介绍ios开发经常遇到的问题和解决方法&#xff0c;包括ios开发证书&#xff0c;ios开发描述文件等。http://kxdang.com/topic/appuploader/ios…

IP报文结构

文章目录 IP报文结构分片 IP报文结构 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4. 4位头部长度(header length): 类似于TCP4位首部长度&#xff0c;通常填的是0101&#xff08;十进制5&#xff09; 16位总长度(total length): IP数据报整体占多少个字节.这用…

大数据企业应用合作解决方案案例

打造产教融合的就业育人的综合服务平台&#xff0c;给予十余年的数据智能产业实践经验&#xff0c;专注于大数据和人工智能方向。 目前合作的企业案例包括&#xff1a;信访大数据平台解决方案、工业废水处理解决方案、找齐远程监控解决方案、道路运输安全、广电用户服务大数据解…

VS2019 c++ cmake项目 打包并使用 (lib\dlll)

背景 最近项目中经常调用第三方库、带头文件、lib和dll的库&#xff0c;需要使用cmake进行项目管理&#xff0c;之前一直比较糊涂这方面&#xff0c;在这里做一个整理总结 编译汇编过程 静态链接方式&#xff1a; 把lib里面编译好的东西&#xff08;函数、变量等&#xff09…

【Yolo】YoloV5训练自定义模型

【Yolo】Jetson Orin Nano下部署 YoloV5 上一篇博文主要记录了在Jetson Orin Nano下部署YoloV5环境&#xff0c;并运行了yoloV5n.pt模型&#xff0c;本篇在上一篇的基础上&#xff0c;进一步记录如何训练自己的目标模型&#xff0c;我们以一根口香糖盒子为训练对象进行说明。 …

Kali Linux 操作系统安装详细步骤——基于 VMware 虚拟机

1. Kali 操作系统简介 Kali Linux 是一个基于 Debian 的 Linux 发行版&#xff0c;旨在进行高级渗透测试和安全审计。Kali Linux 包含数百种工具&#xff0c;适用于各种信息安全任务&#xff0c;如渗透测试&#xff0c;安全研究&#xff0c;计算机取证和逆向工程。Kali Linux 由…

学习笔记(4)页面开发

目录 1&#xff0c;页面开发1.1&#xff0c;标签类1.2&#xff0c;资源引用1.3&#xff0c;页面跳转 2&#xff0c;开发规范2.1&#xff0c;应用生命周期2.2&#xff0c;页面生命周期&#xff1a;2.3&#xff0c;条件编译 3&#xff0c;注意事项 1&#xff0c;页面开发 1.1&am…