顺序表,让数据有序飞舞:C语言实现全攻略

news2024/10/6 15:22:14

在这里插入图片描述

本篇博客会讲解顺序表这种数据结构的相关知识,并且使用C语言实现一个顺序表。

概述

什么是顺序表呢?顺序表是一种线性的数据结构,其特点是:数据是从第一个位置开始,连续存放的。其实,你完全可以把它等价于C语言中的数组。

如果我们想用C语言实现一个顺序表,当然可以直接定义一个静态的数组,比如:int arr[100];。此时,这个“顺序表”就可以存储100个整型数据。但是这样的顺序表其实有很多问题。比如,你怎么知道这个顺序表中有效数据有几个呢?所以,需要定义一个结构体,用size来记录顺序表中有效数据的个数,在插入或者删除元素后,记得更新size。

struct SeqList
{
	int arr[100];
	int size;
};

优化一下:为了以后维护方便,把顺序表的容量100和元素类型int都重新定义一下。为了书写方便,对结构体也typedef一下。

// 顺序表容量
#define N 100
// 顺序表存储元素的数据类型
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType arr[N]; // 存储数据的数组
	int size;          // 有效数据个数
}SeqList;

这就是静态的顺序表的简单定义。但是,这样的顺序表有一个问题:如果数据太多,会存不下;如果数据太少,会造成空间浪费。所以,最好使用动态内存管理的方式,动态地调整数组的大小,用一个指针来记录数组的起始位置。由于数组会动态扩容的,还需要使用capacity来记录此时的容量大小。

// 顺序表存储元素的数据类型
typedef int SLDateType;
// 顺序表
typedef struct SeqList
{
	SLDateType* a; // 指向数组的指针
	int size;      // 有效数据的个数
	int capacity;  // 容量,最多存储的数据个数
}SeqList;

初始化

先实现一个初始化函数,声明如下:

void SeqListInit(SeqList* ps);

初始化时,可以默认先用malloc开一块空间,同时初始化size和capacity。

注意:函数内改变结构体,必须使用结构体的指针,此时这个指针必须指向一块有效的空间,所以可以断言一下。

void SeqListInit(SeqList* ps)
{
	assert(ps);

	// 先开辟4个数据的空间
	ps->a = (SLDateType*)malloc(sizeof(SLDateType) * 4);
	// 检查是否开辟成功
	if (ps->a == NULL)
	{
		// 开辟失败
		perror("malloc fail");
		return;
	}
	// 开辟成功
	ps->capacity = 4;
	ps->size = 0;
}

销毁

接下来实现一个函数来销毁顺序表,声明如下:

void SeqListDestroy(SeqList* ps);

销毁时,需要使用free释放掉动态开辟的空间,防止内存泄漏。

void SeqListDestroy(SeqList* ps)
{
	assert(ps);

	// 释放内存空间
	free(ps->a);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

打印

顺序表中的数据最好能够打印,方便后续的测试。声明一个打印函数:

void SeqListPrint(SeqList* ps);

打印顺序表中的数据,可以使用for循环遍历数组,结束条件由size来控制。

void SeqListPrint(SeqList* ps)
{
	assert(ps);

	// 遍历数组并打印
	for (int i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

插入

接下来是重头戏,实现一个函数,能够在下标为pos的位置插入一个值x。声明如下:

void SeqListInsert(SeqList* ps, int pos, SLDateType x);

函数实现时,需要注意检查pos的有效性。pos必须满足pos>=0 && pos<=ps->size,为什么可以等于size呢?如果等于size,其实就是在顺序表的尾部插入数据,并不违反顺序表中数据连续存放的规则。

如何在pos下标插入一个x呢?由于顺序表中的数据是连续存储的,所以需要先把pos及它后面的数据都向后挪动一格,然后再插入数据。

此时我们可以使用memmove来拷贝重叠的内存块。接下来需要计算清楚需要挪动几个数据。假设顺序表中有6个元素,那么size就是6,再假设pos是3,顺序表中存储的是[1 2 3 4 5 6],也就是说,我们要在4前面插入一个x,就需要把[4 5 6]这3个元素向后挪动一格再插入。如何算出要挪动的数据个数呢?size-pos=6-3=3,即可。

但是还要考虑一个问题:如果size==capacity,就说明顺序表已经放满了,此时就要先扩容。这点我们留给函数SeqListCheckCapacity来实现。

void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	// pos必须在[0, size]之间,如果等于size就是尾插
	assert(pos >= 0 && pos <= ps->size);

	// 检查容量,如果满了就扩容
	SeqListCheckCapacity(ps);

	// 挪动数据,腾出位置
	// 把pos~size-1的数据向后挪动一格
	memmove(ps->a + pos + 1, ps->a + pos, sizeof(SLDateType) * (ps->size - pos));
	// 在pos位置插入数据x
	ps->a[pos] = x;
	// 更新size
	ps->size++;
}

接下来谈谈如何检查容量并扩容。以下是函数声明:

void SeqListCheckCapacity(SeqList* ps);

如果ps->size==ps->capacity,就说明顺序表已经放满了,需要扩容。此时需要对指针a进行realloc,假设把这块动态开辟的空间扩容为原来的2倍,扩容完后还需要记得更新capacity。

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);

	// 判断是否已满
	if (ps->size == ps->capacity)
	{
		// 满了,要扩容
		// 扩容成原来的2倍
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * 2);
		// 检查扩容是否成功
		if (tmp == NULL)
		{
			// 扩容失败
			perror("realloc fail");
			return;
		}
		else
		{
			// 扩容成功
			ps->a = tmp;
			// 更新capacity
			ps->capacity *= 2;
		}
	}
}

有了Insert函数,就可以实现尾插(在顺序表尾部插入数据)和头插(在顺序表头部插入数据),直接复用即可。

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);

	// 在size的位置插入x
	SeqListInsert(ps, ps->size, x);
}

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);

	// 在0下标插入x
	SeqListInsert(ps, 0, x);
}

删除

接下来实现一个函数,来删除下标为pos位置的数据。以下是函数声明:

void SeqListErase(SeqList* ps, int pos);

对pos的检查和Insert函数有所不同,此时pos不能等于size,因为size位置是最后一个有效数据的下一个位置,已经越界了。

删除的逻辑是:把pos位置后面的数据(不包括pos)向前挪动一格,覆盖掉pos位置的数据。那么有多少个数据呢?比前面Insert函数挪动的数据少一个,因为不包含pos位置,要挪动的数据个数是size-pos-1。

void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	// pos必须在[0, size)之间,size已经越界了
	assert(pos >= 0 && pos < ps->size); // 同时保证了size>0

	// 挪动数据,覆盖掉pos
	// 把pos+1~size-1的数据向前挪一格
	memmove(ps->a + pos, ps->a + pos + 1, sizeof(SLDateType) * (ps->size - pos - 1));
	// 更新size
	ps->size--;
}

有了Erase函数,就可以实现尾删(删除顺序表尾部的数据)和头删(删除顺序表头部的数据),直接复用即可。

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	// 检查顺序表是否还有数据
	assert(ps->size > 0);

	// 删除0下标的数据
	SeqListErase(ps, 0);
}

void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	// 检查顺序表是否还有数据
	assert(ps->size > 0);

	// 删除size-1位置的数据
	SeqListErase(ps, ps->size - 1);
}

查找

查找就简单了。函数声明如下:

int SeqListFind(SeqList* ps, SLDateType x);

假设找到了返回下标,找不到返回-1,遍历数组并且依次比对即可。

int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);

	// 遍历数组,查找x
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			// 找到了
			return i;
		}
	}

	// 没找到
	return -1;
}

修改

把pos位置的值修改为x,函数声明如下:

void SeqListModify(SeqList* ps, int pos, SLDateType x);

这个就太简单了,直接用下标修改即可。但是不要掉以轻心,检查pos的范围时,pos不能等于size,因为size是最后一个有效数据的下一个位置,已经越界了。

void SeqListModify(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	// pos必须在[0, size)之间,size已经越界了
	assert(pos >= 0 && pos < ps->size);

	// 把pos位置的数据修改成x
	ps->a[pos] = x;
}

总结

顺序表满足以下规律:

  1. 在顺序表的前半部分插入或者删除数据,由于需要挪动大量的数据,效率较低;不过尾插、尾删的效率很高。
  2. 插入数据时,如果空间不够,需要扩容,而扩容是有代价的。因为realloc函数存在异地扩容的情况,效率较低;除此之外,还可能存在空间的浪费。
  3. 但是顺序表并不是一无是处,它有一个重要的特征:数据在内存中是连续存放的,所以顺序表支持下标的随机访问,像排序、二分查找等算法必须依赖于这一点。数据连续存放还有一个优点,根据局部性原理,CPU的缓存命中率较高。

感谢大家的阅读!

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

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

相关文章

哈工大软件过程与工具作业3

哈尔滨工业大学 计算学部/软件学院 2022年秋季学期 2020级本科《软件过程与工具》课程&#xff08;3.0学分&#xff09; 作业报告 作业3&#xff1a;软件测试报告 姓名 学号 联系方式 石卓凡 120L021011 944613709qq.com/18974330318 目 录 1 作业目的与要求...........…

《计算机网络—自顶向下方法》 Wireshark实验(七):以太网与ARP协议分析

1 以太网 1.1 介绍 以太网是现实世界中最普遍的一种计算机网络。以太网有两类&#xff1a;第一类是经典以太网&#xff0c;第二类是交换式以太网&#xff0c;使用了一种称为交换机的设备连接不同的计算机。 经典以太网&#xff1a;是以太网的原始形式&#xff0c;运行速度从 …

股价与期待值“背道而驰”,友车科技能否站稳科创板?

上周&#xff0c;共计7只新股登陆A股&#xff0c;其中&#xff0c;用友汽车信息科技&#xff08;上海&#xff09;股份有限公司&#xff08;以下简称友车科技&#xff09;于5月11日在科创板上市。发行价为33.99元&#xff0c;发行3607.94万股&#xff0c;募资总额为12.24亿元。…

jmeter接口测试项目实战详解,零基础也能学,源码框架都给你

目录 1.什么是jmeter&#xff1f; 2.jmeter能做什么&#xff1f; 3.jmeter环境搭建 3.1前提&#xff1a; 3.2jmeter下载&#xff1a; 3.3jmeter环境搭建&#xff1a; 3.3.1mac当中jmeter环境搭建&#xff1a; 3.4jmeter基本配置 3.4.1.切换语言 3.4.2.安装插件 4.jmet…

MySQL数据库基础2

文章目录 数据类型表的约束 数据类型 1、数值类型&#xff1a;BIT、TINYINT、BOOL、SMALLINT、INT、BIGINT、FLOAT[(M,D)]、DOUBLE[(M,D)]、DECIMAL[(M,D)] FLOAT[(M,D)]&#xff1a;占用四个字节&#xff0c;M表示显示位数&#xff0c;D表示小数位数&#xff0c;精度保证&am…

5年测试,已失业3个月.....

我做测试5年&#xff0c;一线城市薪水拿到15K&#xff0c;中间还修了一个专升本&#xff0c;这个年限不说资深肯定也是配得上经验丰富的。今年行情不好人尽皆知&#xff0c;但我还是对我的薪水不是很满意&#xff0c;于是打算出去面试&#xff0c;希望可以搏一个高薪。 但真到面…

测试自动化工具_Katalon

测试自动化_Katalon 1.概述 ​ Katalon界面的自动化测试工具&#xff0c;简称KS&#xff0c;于2015年推出。是开源的&#xff0c;提供的版本有免费的版本&#xff0c;还有企业版是收费的。如下图。其中的服务台功能应该是持续继承的支持。可试用一个月。 ​ 最初是支持Web UI…

Chatgpt论文笔记——MiNiGPT4解读

前言 代码地址&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 摘要 摘要写的就很简单了&#xff0c;标黄的是重点&#xff0c;可以看到这个方法很简单&#xff0c;就是拿了一个视觉的encoder&#xff08;Blip-2&#xff09;以及拿了一个文本的encoder&#xff08;Vic…

OpenPCDet系列 | 5.2 PointPillars算法——PointPillarScatter伪图像BEV特征构建模块

文章目录 PointPillarScatter模块1. PointPillarScatter初始化2. PointPillarScatter前向传播 OpenPCDet的整个结构图&#xff1a; PointPillarScatter模块 在进行了PillarVFE编码后&#xff0c;此时的batch_dict更新如下所示&#xff0c;追加了pillar_features字段&#xf…

好程序员:转行学Java怎么样?什么工作可以月入过万?

去年的时候有个学妹跟小源说&#xff0c;想转行&#xff0c;但是目前又比较迷茫&#xff0c;不知道该从事啥行业&#xff1f;她跟小源说了下具体情况&#xff0c;本科学历&#xff0c;Java零基础。小源让好程序员的就业老师跟她分享了下相关的it行业规划&#xff0c;最后她学了…

Python实现ACO蚁群优化算法优化循环神经网络分类模型(LSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蚁群优化算法(Ant Colony Optimization, ACO)是一种源于大自然生物世界的新的仿生进化算法&#xff0c…

关于摄影艺术欣赏分享(私密空间+玛丽娜·阿布拉莫维奇+世界十大最昂贵的摄影作品欣赏+《死亡路上的对话》+马良作品)

文章的内容灵感是旁听课而来的呀&#xff0c;搜了很多相关文章很喜欢 1. 出去遛狗&#xff0c;晚点回来 国外文化中国文化&#xff08;国外不可以随意拍摄照片&#xff09; 公共空间私密空间 艺术的价值在于可以给人们思考&#xff0c;颠覆常识 2. 行为艺术之母玛丽娜阿布拉…

YOLOv5【目录结构源码】超详细注释解读!!!建议收藏✨✨!

上一篇文章介绍了YOLOv5的网络的详细解读&#xff1a;直通车&#x1f680; 由于以后的学习及在进行的项目打算YOLOv5再仔细研究进行使用及改进&#xff0c;接下来会出相关于YOLOv5的代码逐行解读以及注释&#xff0c;废话不多说&#xff0c;让我们一起学习YOLOv5源码吧&#xf…

CTFshow pwn03

题目&#xff1a; 在做本道题前&#xff0c;我们先了解所必要的知识 Libc是什么&#xff1f; 英文名字&#xff1a;Standard C library&#xff0c;其翻译过来&#xff0c;C语言标准库 它是符合ANSI C标准的一个函数库 学过C语言会明白 ANSI C标准又是什么&#xff1f; 198…

Redo log

目录标题 前言为什么需要redo log redo log中的WAL&#xff08;先写日志&#xff0c;再写磁盘&#xff09;重要参数innodb_flush_log_at_trx_commit如何选择 redo log记录形式日志块(log block)redo log的格式 redo log何时刷入磁盘正常关闭服务器时事务提交时&#xff08;inno…

【STL】string的使用

放在专栏【C知识总结】&#xff0c;会持续更新&#xff0c;期待支持&#x1f339; STL简介 STL的诞生 STL为英文Standard Template Library的缩写&#xff0c;译为标准模板库。是C标准库的重要组成部分。 长久以来&#xff0c;软件届一直希望建立一种可重复运用的东西。所谓…

首个支持RWA交易的订单簿DEX-PoseiSwap,即将开启IEO

随着 DeFi 世界的发展&#xff0c;越来越多的链上协议支持以合成资产的方式&#xff0c;将传统金融资产以加密资产的形式映射至链上&#xff0c;包括美股、黄金期货等等&#xff0c;虽然这种方式进一步帮助投资者&#xff0c;以非许可的形式丰富了投资标的&#xff0c;但这种方…

GraphPad Prism 9.5.1 for Mac 操作简便功能强大且实用的医学绘图分析工具

GraphPad Prism简介 GraphPad Prism是一款非常实用的统计软件&#xff0c;其功能非常强大&#xff0c;能够帮助用户进行各类科研数据的处理和分析&#xff0c;快速绘制出各种专业的图像和数据报告。 GraphPad Prism软件的用户界面非常友好&#xff0c;易于学习和操作&#xf…

azkaban 安装 使用

目录 拷贝安装包 解压 改名 修改MySQL配置文件 创建数据库 导入数据表 修改配置文件 修改azkaban-exec下面文件 修改azkaban.properties文件 修改 commonprivate.properties 文件 修改MySQL的jar包 启动 azkaban-exec 修改azkaban-web下面文件 修改azkaban.prop…

Amazon EKS 上有状态服务启用存储加密

1.背景 用户通过 Deployment, Replication Controller 可以方便地在 Kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据&#xff0c;通过简单的负载均衡策略可实现请求分发。 Deployment 以及 Replication Controller 是为无状态服务而设计的…