数据结构2——单链表

在数据结构1——顺序表(C语言版)中,我们已经了解了顺序表的使用和实现,总结一下顺序表的优点:

①尾插尾删效率足够快;

②下标的随机访问和修改也足够方便。

可除此之外顺序表也确实存在着不足:

①头部和中部的插入删除效率都不高(时间复杂度为O(N));

②扩容需要申请新空间,拷贝数据,释放旧空间,有一定的消耗;

③扩容可能存在空间浪费(我们的扩容函数是2倍增长,比如:当前容量是100,我需要再插入3个数据,按照我的2倍扩容机制就会扩容到200,这时就会浪费了97个数据的空间)。

了解了顺序表的不足,下面我们就来学习一下链表,看一看链表能不能解决顺序表的不足。


目录

1.链表

1.1链表的概念及结构

1.2 链表的分类

2.无头单链表的实现

1. 节点

2.遍历链表

3.动态增加新节点

4.查找(修改)

5.插入

5.1 尾插

5.2 头插

5.3 在pos之前插入x

5.4 在pos之后插入x 

6.删除

6.1 尾删

6.2 头删

6.3 删除pos位置

6.4 删除pos的后一个位置

7.测试(仅测试一个)

源代码

SList.h

SList.c

test.c


1.链表

为了避免像顺序表那样插入数据时造成扩容浪费,那我就边插入边扩容行不行呢?只要插入一个新数据我就开一块空间存一个。那么问题来了,如果这么搞,这些数据就不连续了啊,那还怎么像顺序表那样成为一个表结构呢?~~~~~~对!不要忘了指针,我们可以用指针把这写不连续的空间串起来啊!

1.1链表的概念及结构

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

也就是说,链表并不像顺序表那样在物理空间上是连续存储的,链表的每一个单位里存的不仅仅有数据域,还存的有指针域,我们把每一个这样的单位称为节点。

如图:

1.2 链表的分类

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

①单向或者双向

②带头或者不带头 

③循环或者非循环

④最常用的两个

1.无头单向非循环链表

结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构。

2.带头双向循环链表

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

2.无头单链表的实现

1. 节点

经过上面的分析我们得知,链表的主要结构为节点,所以我们先用结构体定义节点:

//定义节点
typedef int SLTDataType;//typedef节点的数据域

typedef struct SListNode
{
	SLTDataType data;//定义节点的数据域
	struct SListNode* next;//定义指向下一个节点的指针
}SLTNode;

2.遍历链表

//遍历链表函数实现
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//定义当前节点
	//while (cur != NULL)//等于空就结束
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;//将下一个节点的地址(指针)赋值给当前节点
	}

	printf("NULL\n");
}

3.动态增加新节点

//增加新节点函数实现
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);//malloc为空直接退出
	}

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

	return newnode;
}

4.查找(修改)

//查找下标pos函数实现
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

5.插入

5.1 尾插

//尾插函数实现
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	

	SLTNode* newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		//改变结构体的指针,所以要用到指针的指针
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;//定义尾节点
		while (tail->next != NULL)//只有尾节点的next指针为空
		{
			tail = tail->next;
		}
		//改变结构体,只需用到结构体的指针即可
		tail->next = newnode;
	}
}

5.2 头插

//头插函数实现
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

5.3 在pos之前插入x

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

5.4 在pos之后插入x 

//在pos之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);
	pos->next = newnode;
	newnode->next = pos->next;
}

6.删除

6.1 尾删

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//1、空
	assert(*pphead);
	//2、一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//3、一个以上节点   找尾
	{
		SLTNode* tailPrev = NULL;//将要尾删的前一个节点的指针域置空
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tailPrev = tail;
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;
	}
}

6.2 头删

//头删函数实现
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);

	//空
	assert(*pphead);
	//非空
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}

6.3 删除pos位置

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

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

6.4 删除pos的后一个位置

//删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	//检查pos是否为尾节点
	assert(pos->next);

	SLTNode* posNext = pos->next;

	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}

7.测试(仅测试一个)

int main() 
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	return 0;
}



*源代码

SList.h

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

//定义节点
typedef int SLTDataType;//typedef节点的数据域

typedef struct SListNode
{
	SLTDataType data;//定义节点的数据域
	struct SListNode* next;//定义指向下一个节点的指针
}SLTNode;

//遍历链表函数声明
void SLTPrint(SLTNode* phead);

//增加新节点函数声明
SLTNode* BuySListNode(SLTDataType x);

//尾插函数声明
void SLTPushBack(SLTNode** phead, SLTDataType x);
//头插函数声明
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//尾删函数声明
void SLTPopBack(SLTNode** pphead);
//头删函数声明
void SLTPopFront(SLTNode** pphead);

//查找下标pos函数声明
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入x
void SLTInsertAfter( SLTNode* pos, SLTDataType x);

//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos);

SList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "SList.h"

//遍历链表函数实现
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//定义当前节点
	//while (cur != NULL)//等于空就结束
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;//将下一个节点的地址(指针)赋值给当前节点
	}

	printf("NULL\n");
}

//增加新节点函数实现
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);//malloc为空直接退出
	}

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

	return newnode;
}

//尾插函数实现
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	

	SLTNode* newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		//改变结构体的指针,所以要用到指针的指针
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;//定义尾节点
		while (tail->next != NULL)//只有尾节点的next指针为空
		{
			tail = tail->next;
		}
		//改变结构体,只需用到结构体的指针即可
		tail->next = newnode;
	}
}
//头插函数实现
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删函数声明
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//1、空
	assert(*pphead);
	//2、一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//3、一个以上节点   找尾
	{
		SLTNode* tailPrev = NULL;//将要尾删的前一个节点的指针域置空
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tailPrev = tail;
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;
	}
}
//头删函数实现
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);

	//空
	assert(*pphead);
	//非空
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}

//查找下标pos函数实现
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

//在pos之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);
	pos->next = newnode;
	newnode->next = pos->next;
}

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

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	//检查pos是否为尾节点
	assert(pos->next);

	SLTNode* posNext = pos->next;

	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "SList.h"

int main() 
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

随手记:牛回速归

上周-国庆前&#xff1a;牛回速归 国庆&#xff1a;小心被套住 国庆后&#xff1a;一片迷茫 总结&#xff1a;要是上周到国庆前的基本都能捞到&#xff0c;后面情况不好说 后续持续更新

word中的表格全部设置宽度100%

1、背景 我们用工具将数据库或其他的数据导出成word时&#xff0c;表格有的会大于100%&#xff0c;超过了边界。word没有提供全局修改的方法。如果我们想改成100%。 一种方式是通过宏&#xff0c;全局改。一种是手动改。 2、宏修改 如果表格多&#xff0c;可以通过这种方式。…

面试中考察栈和队列的经典算法题

&#x1f49d;&#x1f49d;&#x1f49d;如果你对顺序表的概念与理解还存在疑惑&#xff0c;欢迎观看我之前的作品&#x1f449;【栈和列队详解】 上篇文章&#x1f449; 【面试中顺序表常考的十大题目解析】 目录 &#x1f4af;前言 &#x1f4af;栈相关题目 ⭐有效的括号…

双十一好物清单有哪些值得入手?双11好物种草宝藏清单分享

随着双十一购物狂欢节的临近&#xff0c;各种优惠和折扣让人目不暇接&#xff0c;在这个充满诱惑的时刻&#xff0c;如何挑选出真正值得入手的好物&#xff0c;成为了许多消费者的难题&#xff0c;为此&#xff0c;我精心整理了一份双十一好物清单&#xff0c;为大家提供一些实…

Linux 网络配置 (深入理解)

前言 前期我比较迷惑Ubuntu 的网络配置。 我接触比较多的 Linux 发行版都是 Ubuntu &#xff0c;我按照网上的一些教程配置网络发现&#xff0c;没有相关网络配置文件夹。然后我发现不是我的问题而是不同版本的配置方式和工具是不一样的。然后有些配置已经弃用了。 常见的网络…

计算机网络--TCP、UDP抓包分析实验

计算机网络实验 目录 实验目的 实验环境 实验原理 1、UDP协议 2、TCP协议 实验具体步骤 实验目的 1、掌握使用wireshark工具对UDP协议进行抓包分析的方法&#xff0c;掌握UDP协议的报文格式&#xff0c;掌握UDP协议校验和的计算方法&#xff0c;理解UDP协议的优缺点&am…

手把手教你使用YOLOv11训练自己数据集(含环境搭建 、数据集查找、模型训练)

一、前言 本文内含YOLOv11网络结构图 训练教程 推理教程 数据集获取等有关YOLOv11的内容&#xff01; 官方代码地址&#xff1a;https://github.com/ultralytics/ultralytics/tree/main/ultralytics/cfg/models/11 二、整体网络结构图 三、环境搭建 项目环境如下&#xf…

verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)

在以往采用 FPGA 实现的 FIR 滤波功能&#xff0c;滤波器系数是通过 matlab 计算生成&#xff0c;然后作为固定参数导入到 verilog 程序中&#xff0c;这尽管简单&#xff0c;但灵活性不足。在某些需求下&#xff08;例如捕获任意给定台站信号&#xff09;需要随时修改滤波器的…

FPGA学习(1)-mux2,2选1多路器

目录 1 开发板配套资料 1.1学习网址和资料网址 2.创建工程文件 2.1创建过程 2.2写程序及仿真测试 2.2.1 写程序生成电路 2.2.2仿真 2.2.3 生成执行文件并烧录 3.实验现象 买的小梅哥店铺的开发板&#xff1a;xc7z020clg400 看的小梅哥的视频&#xff1a;03C _基于ZYN…

VUE 开发——AJAX学习(三)

一、async函数和await async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为&#xff0c;而无需刻意地链式调用Promise async写在函数声明的前面&#xff1b;在async函数内&#xff0c;使用await关键字&#xff0c;获取Promise对象“成功状态”结果值 &…

身份证号、定位信息等个人信息敏感性判定解析

关于身份证号号码以及精确定位信息是否是敏感个人信息的疑问一直以来不少合规安全从业者有疑惑&#xff0c;本文来自于《数安标准强基助力计划 》作者为指南和标准的起草者&#xff0c;其观点具有一定的权威性&#xff0c;一下为内容摘要&#xff0c;以供大家学习和解惑&#x…

Qt多线程操作sqlite数据库

问题 就是为了多线程操作sqlite数据库,为什么,因为数据库是耗时的操作,一条数据的插入,差不多200ms,如果是数据插入多了,界面会有明显的卡顿,因此必须,多线程操作数据库。 问题是这样的: 插入数据之后,接着更新界面;然而,插入数据是比较耗时的操作,尤其插入数据…

图解C#高级教程(一):委托

什么是委托 可以认为委托是持有一个或多个方法的对象。但它与对象不同&#xff0c;因为委托可以被执行。当执行委托时&#xff0c;委托会执行它所“持有”的方法。先看一个完整的使用示例。 // See https://aka.ms/new-console-template for more informationdelegate void M…

【Git原理与使用】Git初识基本操作

Git初识&&基本操作 1.初识Git2.Git安装3.Git基本操作3.1创建Git本地仓库3.2配置Git3.3认识工作区、暂存区、版本库3.4添加文件3.5修改文件3.6版本回退3.7撤销修改3.8删除文件 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f…

Vscode超好看的渐变主题插件

样式效果&#xff1a; 插件使用方法&#xff1a; 然后重启&#xff0c;之后会显示vccode损坏&#xff0c;不用理会&#xff0c;因为这个插件是更改了应用内部代码&#xff0c;直接不再显示即可。

基于nodejs+vue的游戏陪玩系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

银河麒麟服务器:更新软件源

银河麒麟服务器&#xff1a;更新软件源 1、使用场景2、操作步骤3、注意事项 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 1、使用场景 当需要安装最新软件或修改软件源配置后&#xff0c;需更新软件源以获取最新软件包信息。 2、操作步…

京东PMO段敬受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 京东集团技术委员会PMO组研发项目经理段敬女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“项目经理如何组织高效的项目会议”。大会将于10月26-27日在北京举办&#xff0c;…

亚信安全天穹5分钟勒索体检 免费试用今起上线

对于勒索攻击的认知 你是否还停留在“2.0时代”&#xff1f; 勒索攻击无疑是企业面临的最大威胁&#xff0c;2024年上半年&#xff0c;勒索组织数量同步增长超过50%&#xff0c;勒索攻击数量也持续攀升&#xff0c;平均勒索赎金突破520万美元。 当前&#xff0c;勒索攻击治理…

java学习-idea编辑器基础使用设置

首先打开电脑中的idea编辑器&#xff0c;点击头部&#xff1a;File按钮 → Settings… 打开设置界面&#xff1b; 设置idea的主题 设置idea代码注释的字体颜色 设置idea编辑器的字体和字体大小 设置idea通过提示回车自动导入包 设置idea输入忽略大小写进行提示