【数据结构】顺序表的基本操作实现

文章目录

  • 前言
  • 一、顺序表的概念及结构
  • 二、顺序表的基本操作实现
    • 1.顺序表的创建
    • 2.顺序表初始化
    • 3.顺序表扩容
    • 4.头插和尾插
    • 5.头删和尾删
    • 6.查找
    • 7.任意位置插入
    • 8.任意位置删除
    • 9.顺序表的销毁
  • 三、总结

前言

从本篇我们开始学习数据结构初阶的内容。

首先我们了解一下什么是线性表:

线性表是n个具有相同特性的数据元素的有限序列。

常见的线性表有:顺序表(Sequence List)、链表(Linked List)、栈(Stack)、队列(Queue)、字符串(String)等
堆(Heap)、二叉树(Binary Tree)、图(Graph) 这些是非线性结构。

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表的存储结构主要分为两种:顺序存储结构和链式存储结构。

而今天我们主要讲解的是线性表中的顺序表

一、顺序表的概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,这种存储结构称为顺序存储结构。顺序表一般情况下采用数组存储,在数组上完成数据的增删查改等基本操作。

二、顺序表的基本操作实现

1.顺序表的创建

顺序表是用数组来进行存储的,因此我们可以在一个结构体中使用一个数组来存储数据,一个变量来记录数据个数。

静态顺序表:使用定长数组存储元素

typedef int SLDataType;//方便后续随时改数据类型
#define N 100 //数组最大长度

typedef struct SeqList
{
	SLDataType a[N];
	int size;	//有效数据个数
}SL;//命名为SL

但静态顺序表只适用于确定知道需要存多少数据的场景。
因为静态顺序表的数组长度N如果定大了,空间开多了浪费;N太小了则不够用。

所以我们一般使用动态顺序表,避免空间的浪费的同时也能随时扩容。

动态顺序表:根据需要来动态地分配空间大小。

#define INIT_SZ 10 //初始空间大小
#define INC_SZ 4   //每次扩容的数量
typedef int SLDataType;//方便改存储的数据类型

typedef struct SeqList
{
	SLDataType* a;
	int size;	//有效数据个数
	int capacity;//空间容量
}SL;//命名为SL

2.顺序表初始化

void SLInit(SL* ps)
{
	assert(ps);
	SLDataType* ptr = (SLDataType*)calloc(INIT_SZ, sizeof(SLDataType));
	if (NULL == ptr)
	{
		perror("calloc");//打印错误信息
		return;
	}
	ps->a = ptr;
	ps->size = 0; //初始存储的数据个数为0
	ps->capacity = INIT_SZ; //初始容量
}

assert()函数如果传入参数是空(NULL),vs会报错并显示具体行数。头文件#include<assert.h>。这样写可以提高代码的健壮性。

我们可以用malloc或者calloc来动态开辟数组,calloc能直接将开辟的空间初始化为0。
忘了怎么使用的小伙伴,可以复习一下另一篇动态内存管理的详解。

3.顺序表扩容

因为我们初始化时只开辟了一定的空间大小,后续在插入数据的时候,要检查可用空间是否足够,不够的话我们需要对原有空间进行扩容。

//检查容量是否已满
void CheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)//数据个数与容量相等说明可用空间已满,需要扩容
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->a, (ps->capacity + INC_SZ) * sizeof(SLDataType));
		if (NULL == tmp)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity += INC_SZ;
	}
}

用realloc进行扩容,每次扩容的大小可以根据需要来改变,每次扩容一定数量,或者扩为原有空间的2倍。

4.头插和尾插

顺序表是用一维数组来存储的,顺序表的基本操作都是在数组的基础上进行的。

我们要对一个一维数组进行头插,也即在第一个元素前面插入一个数据,需要将数组整体往后挪一位。
具体方法为:数组从后往前,依次往后移动一位。只能从后往前,如果从前往后会覆盖掉原有数据。

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	CheckCapacity(ps);//检查容量,如果已满则扩容
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;//插入数据
	ps->size++;	//有效数据个数+1
}

而顺序表尾插就较为简单,直接在末尾插入。

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	CheckCapacity(ps);
	ps->a[ps->size++] = x;
}

注意: 后面我们介绍完 在任意位置插入和删除操作后,头插尾插以及头删尾删都可以用这两个操作完成。
之所以详细写出头插尾插以及头删尾删的具体实现是为了方便大家理解。

5.头删和尾删

头删则需要将数组中每一个元素向前挪动一位。从前往后,将后一个位置的元素赋值给当前位置

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;//有效个数-1
}

尾删只需要将顺序表长度-1即可。

但是注意,删除的空间不能free掉,因为动态开辟的空间不能局部释放。

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//free(ps->a[ps->size-1]);//不能这样写!!!
	ps->size--;
}

6.查找

查找函数可以根据数值找到对应的下标并返回,不存在则返回-1

int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

当然也可以根据下标返回对应的数值

int SLFind(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//查找的下标要在有效范围内
	return ps->a[pos];
}

7.任意位置插入

插入函数可以在下标为pos的位置插入数据,需要将pos位置及后面的数据的往后移一位,然后将插入数据存放在pos位置处。

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//插入的位置要符合逻辑
	CheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}

实现SLInsert函数后,我们的头插和尾插函数可以用一行代码替代。

SLInsert(ps, 0, x);	//头插
SLInsert(ps, ps->size, x);//尾插

8.任意位置删除

删除pos位置的数据,需要将pos后面的数据整体往前移一位。
往前移需要从前往后遍历顺序表,往后移需要从后往前遍历顺序表。

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

至此,我们的头删和尾删函数也可以用一行代码替代。

SLErase(ps, 0);//头删
SLErase(ps, ps->size - 1);//尾删

在顺序表的具体实现时,我们就不用再写头插、尾插、头删、尾删了,一个插入函数和一个删除函数足矣。

9.顺序表的销毁

别忘了,我们的顺序表是动态开辟的, 动态开辟空间使用完之后一定要及时free释放,否则会导致内存泄漏。

void SLDestroy(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
	ps = NULL;
}

三、总结

顺序表属于顺序存储结构,物理空间连续,一般是以数组的形式存储的。

顺序表的优点

可以随机访问任意位置的元素,效率为O(1),尾插尾删效率很高。

顺序表的缺点

1.中间或者头部的插入删除效率很低,时间复杂度为O(N)。
2.扩容有一定的消耗,可能会有一定空间的浪费。

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

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

相关文章

【错题集-编程题】十字爆破(预处理 + 模拟)

牛客对于题目链接&#xff1a;十字爆破 (nowcoder.com) 一、分析题目 暴力模拟会超时。 预处理&#xff0c;先把每一行以及每一列的和存起来。 模拟即可&#xff0c;但是由于数据量过⼤&#xff0c;我们可以提前把每⼀⾏以及每⼀列的和存起来&#xff0c;⽅便统计总和。 二、代…

应用分层和企业规范

目录 一、应用分层 1、介绍 &#xff08;1&#xff09;为什么需要应用分层&#xff1f; &#xff08;2&#xff09;如何分层&#xff1f;&#xff08;三层架构&#xff09; MVC 和 三层架构的区别和联系 高内聚&#xff1a; 低耦合&#xff1a; 2、代码重构 controlle…

Sqlserver批量迁移Job

因为切换物理机&#xff0c;需要把数据库的作业从A机器迁移到B机器&#xff0c;数据库整体备份还原就可以了&#xff0c;数据库上的作业不会跟着带过去&#xff0c;需要手动创建&#xff0c;作业数量太多&#xff0c;逐一创建太浪费时间&#xff0c;Microsoft SQL Server Manag…

SpringBoot+Vue项目企业客户管理系统

一、前言介绍 本文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述企业客户管理系统的当前背景以及系统开…

扩展学习|国内外用户画像相关进展一览

文献来源&#xff1a;徐芳,应洁茹.国内外用户画像研究综述[J].图书馆学研究,2020(12):7-16.DOI:10.15941/j.cnki.issn1001-0424.2020.12.002. 一、用户画像的概念 用户画像概念一经提出,便被广泛应用到精准营销等领域。后来,作为一种描绘用户特征、表达用户诉求的有效工具,用户…

karpathy Let‘s build GPT

1 introduction 按照karpathy的教程&#xff0c;一步步的完成transformer的构建&#xff0c;并在这个过程中&#xff0c;加深对transformer设计的理解。 karpathy推荐在进行网络设计的过程中&#xff0c;同时利用jupyter notebook进行快速测试和python进行主要的网络的构建。 …

前端页面平滑过渡解决方案

一、问题产生 在使用图片作为页面背景时&#xff0c;无法使用transtion进行平滑过渡&#xff0c;直接切换背景又会降低使用体验。 二、解决方式 使用clip-path对背景图片裁剪配合transtion实现平滑过渡的效果 三、效果展示 网址&#xff1a;ljynet.com 四、实现方式 tem…

图像特征点检测

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

练习题(2024/5/3)

1对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false提示&#xff1a; 树中…

前端工程化04-VsCode插件设置总结(持续更)

1、输出语句log设置 log输出、平常你输出log,还必须得打一个console然后再.log()非常不方便&#xff0c;当然我们可以直接输入一个log,但是提示有两个&#xff0c;我们还得上下选择 所以我们直接采用插件的提示 一个clg就可以了 2、括号包裹提示 找到VsCode的settings.js文…

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 &#xff1e;什么是研究生入学考试&#xff1f; 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后&#xff0c;也可称研究生&#xff0c;含义为研究生学历的人。在中国大陆地区&#xff0c;普通民众一般也将硕士毕业生称…

微图乐 多种装B截图一键制作工具(仅供娱乐交流)

软件介绍 采用exe进程交互通信。全新UI界面&#xff0c;让界面更加清爽简约。支持zfb、VX、TX、Yin行、Dai款、游戏等图片生成&#xff0c;一键超清原图复制到剪辑板&#xff0c;分享给好友。适用于提高商家信誉度&#xff0c;产品销售额度。装逼娱乐&#xff0c;用微图乐。图…

InfiniFlow 創始人兼CEO張穎峰確認出席“邊緣智能2024 - AI開發者峰會”

隨著AI技術的迅猛發展&#xff0c;全球正逐步進入邊緣計算智能化與分布式AI深度融合的新時代&#xff0c;共同書寫著分布式智能創新應用的壯麗篇章。邊緣智能&#xff0c;作為融合邊緣計算和智能技術的新興領域&#xff0c;正逐漸成為推動AI發展的關鍵力量。借助分布式和去中心…

扫雷实现详解【递归展开+首次必展开+标记雷+取消标记雷】

扫雷 一.扫雷设计思路二.扫雷代码逐步实现1.创建游戏菜单2.初始化棋盘3.打印棋盘4.随机布置雷5.统计周围雷的个数6.递归展开棋盘7.标记雷8.删除雷的标记9.保证第一次排雷的安全性棋盘必定展开10.排查雷11.判断输赢 三.扫雷总代码四.截图 一.扫雷设计思路 1.创建游戏菜单。  2.…

使用机器学习确定文本的编程语言

导入必要的库 norman Python 语句&#xff1a;import <span style"color:#000000"><span style"background-color:#fbedbb"><span style"color:#0000ff">import</span> pandas <span style"color:#0000ff&quo…

Postman的一些使用技巧

Postman 是一个流行的 API 开发工具&#xff0c;用于设计、开发、测试、发布和监控 API。在现代web开发中使用非常广泛。后端开发必备而且必会的工具。 目录 1.配置环境变量 2.动态变量 3.脚本 4.测试 5.模拟 6.监控 7.集合运行器 8.响应保存 9.请求历史 10.同步请求…

基于springboot+vue+Mysql的影城管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

brpc profiler

cpu profiler cpu profiler | bRPC MacOS的额外配置 在MacOS下&#xff0c;gperftools中的perl pprof脚本无法将函数地址转变成函数名&#xff0c;解决办法是&#xff1a; 安装standalone pprof&#xff0c;并把下载的pprof二进制文件路径写入环境变量GOOGLE_PPROF_BINARY_PA…

现代循环神经网络(GRU、LSTM)(Pytorch 14)

一 简介 前一章中我们介绍了循环神经网络的基础知识&#xff0c;这种网络 可以更好地处理序列数据。我们在文本数据上实现 了基于循环神经网络的语言模型&#xff0c;但是对于当今各种各样的序列学习问题&#xff0c;这些技术可能并不够用。 例如&#xff0c;循环神经网络在…

Java中接口的默认方法

为什么要使用默认方法 当我们把一个程序的接口写完后 用其他的类去实现&#xff0c;此时如果程序需要再添加一个抽象方法的时候我们只有两种选择 将抽象方法写在原本的接口中 但是这样写会导致其他所有改接口的实现类都需要实现这个抽象方法比较麻烦 写另一个接口 让需要的实…