C语言数据结构初阶(2)----顺序表

news2024/11/20 10:26:15

目录

1. 顺序表的概念及结构

2. 动态顺序表的接口实现

 2.1 SLInit(SL* ps) 的实现

2.2 SLDestory(SL* ps) 的实现

2.3 SLPrint(SL* ps) 的实现

2.4 SLCheckCapacity(SL* ps) 的实现

2.5 SLPushBack(SL* ps, SLDataType x) 的实现

2.6 SLPopBack(SL* ps) 的实现

2.7 SLPushFront(SL* ps, SLDataType x) 的实现

2.8 SLPopFront(SL* ps) 的实现

2.9 SLInsert(SL* ps, int pos, SLDataType x) 的实现

2.10 SLErase(SL* ps, int pos) 的实现

2.11 SLFind(SL* ps, SLDataType x) 的实现

3. 顺序表的缺点

 

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

1. 顺序表的概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组如果数组如果太大了,会出现浪费的情况,如果定长数组太小了,则会出现不够用的情况。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

2. 动态顺序表的接口实现

下面是顺序表的头文件哈:SeqList.h

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

typedef int SLDataType;

//一开始的空间
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
	SLDataType* a;
	int size;     // 有效数据个数
	int capacity; // 空间容量
}SL;

// 增删查改
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
void SLCheckCapacity(SL* ps);

void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);

这个头文件里面的结构体需要好好理解一下的哦。SLDatatype* a,是指向动态开辟的数组的指针。size,是指顺序表中的元素个数。capacity,是指动态的数组有多大的空间。

 2.1 SLInit(SL* ps) 的实现

在创建顺序表的时候显然就是用上面的那个结构体创建,即:SL s。然后我们就要将结构体传过去对结构体进行初始化,这时显然就有两种传参的方式:直接传结构体 s 过去,这样做固然没有啥大的问题,但是嘞,我们都知道在传参时是需要将实参进行拷贝的,如果结构体本身越大,消耗的时间也就越多。所以在结构体传参的时候我们一般传结构体的指针。即,&s。初始化时不用做什么大事儿,只需要开辟一个小小的空间:INIT_CAPACITY (头文件中有该标识符的定义),将 size 和 capacity 赋值即可。

关于传参的问题请参考:http://t.csdn.cn/4GlPu

//顺序表的初始化
void SLInit(SL* ps)
{
	//断言提高代码的鲁棒性,因为后面有对ps指针的解引用,所以不能传空指针哈
	assert(ps);
	//开辟一个小小的空间
	ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
	//必要性的检查
	if (ps->a == NULL)
	{
		//如果开辟失败的话报出错误
		perror("malloc fail");
		return;
	}
	//size和capacity的初始化
	ps->size = 0;
	ps->capacity = INIT_CAPACITY;
}

2.2 SLDestory(SL* ps) 的实现

这个就是当顺序表使用完毕后必须调用的函数。因为顺序表的空间是在堆区上去申请的,如果说程序员不释放的话,就会出现内存泄漏的问题。虽然说这里的顺序表使用的空间不释放,在进程结束时操作系统会回收,但如果是跑在服务器上的程序,就会出问题的,所以说一定要养成好习惯。堆上开辟的空间必须手动释放哦!

//顺序表的销毁
void SLDestroy(SL* ps)
{
	assert(ps);

	//与malloc对应的free
	free(ps->a);
	//养成好习惯,不要出现野指针
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

2.3 SLPrint(SL* ps) 的实现

这个是顺序表的打印函数哈,这个函数比较简单,只需要用一个变量打印size之前的数据即可。

//顺序表的打印
void SLPrint(SL* ps)
{
	assert(ps);
	//从头到size打印数据
	for (int i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

2.4 SLCheckCapacity(SL* ps) 的实现

我们往顺序表里面插数据的时候,显然可能存在没有剩余空间的时候。这时我们就需要扩容啦。

emm,扩容的时候新的空间应该选多大嘞,有多种选择哈,这里我们选则扩原来的两倍。

 我们都知道 realloc 在扩容的时候是有三种情况的:

1:扩容失败。这种情况不用讨论哈。

2:原地扩容,即从原空间的起始指针开始,还有新空间的大小还未被使用。此时 realloc 返回的指针就是原来的指针。

 3:异地扩容,异地扩容就是从原来的指针开始,没有你需要的空间能够分配给你,这时就会在堆区寻找空间足够的地方,然后将原来的数据拷贝到新的空间,并且销毁原来的空间,返回新的空间的起始地址。

 扩容之后更新一下 capacity 就可以啦!

//检查顺序表的容量
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	//size == capacity 说明数据装满了,需要扩容
	if (ps->size == ps->capacity)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			//这里的道理根SLInit中的一样
			perror("realloc fail");
			return;
		}
		//这里一定要将tmp赋值给ps->a,因为可能异地扩容
		ps->a = tmp;
		//这里我们是选用数据满了扩容两倍这种方式的
		ps->capacity *= 2;
	}
}

2.5 SLPushBack(SL* ps, SLDataType x) 的实现

这个函数只需要注意一下尾插的时候检查一下顺序表是不是满了就行。尾插就是在数组下标为size的地方插入数据即可,插入数据后 size++。不理解可以看看前面的图。

//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);

	//检查容量,不足则扩容
	SLCheckCapacity(ps);
	ps->a[ps->size++] = x;

}

2.6 SLPopBack(SL* ps) 的实现

这是顺序表的尾删哈,这个就更简单了,只要将 size-- 就行,原因就是我们访问数据只能访问到下标小于size的哒。还有一点就是如果顺序表为空,即size == 0 是不允许删除数据的哦!

//顺序表的尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	//顺序表为空不允许删除数据哦
	assert(ps->size > 0);

	ps->size--;

}

2.7 SLPushFront(SL* ps, SLDataType x) 的实现

这是顺序表的头插函数哈,这里就需要将数据整体向后移动一位,才可以进行头插哦,记得一定要从最后一个数据开始移动哦!!头插完毕记得让size++哦!既然是插入元素也要记得检查顺序表的容量哦!

//顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	SLCheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[0] = x;
	ps->size++;

}

2.8 SLPopFront(SL* ps) 的实现

这里和头插一样同样需要移动数据只不过是从前往后移动数据。这里就不画图演示了!!!头删之后记得将 size-- 哦!!还有一点,没有数据不允许删除数据哦!

//顺序表的头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	int begin = 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	ps->size--;
}

2.9 SLInsert(SL* ps, int pos, SLDataType x) 的实现

这是顺序表的指定位置插入哦,0 <= pos <= size 的哦,pos的输入必须合法。这里的插入和头插的逻辑类似,也需要移动数据,从后往前移动,到pos的位置为止。然后插入数据 x,同时size++,插入数据都必须检查容量的。

//顺序表指定位置的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//pos输入必须合法
	assert(pos >= 0 && pos <= ps->size);
	//检查容量
	SLCheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[pos] = x;
	ps->size++;
}

2.10 SLErase(SL* ps, int pos) 的实现

这里的逻辑就和头删的数据移动差不多的,从pos的位置开始,从前往后移动。移动完了记得让 size--。这里同样需要检查 pos 的合法性。但是就不用检查顺序表是否为空啦,因为当顺序为空的时候 size == 0,pos无论输入什么值都会断言失败的。

//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	//pos输入合法
	assert(pos >= 0 && pos < ps->size);

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	ps->size--;
}

2.11 SLFind(SL* ps, SLDataType x) 的实现

//顺序表查找指定元素,返回下标,不存在返回-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;
}

3. 顺序表的缺点

1. 中间/头部的插入删除,时间复杂度为O(N)。
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的时间消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如200,我们再继续插入了5个数据,后面没有数据插入了,就会浪费空间。
思考:如何解决以上问题呢?下期的链表可以帮助大家解决其中的一部分问题。

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

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

相关文章

“XXX.app 已损坏,打不开。您应该将它移到废纸篓”,Mac应用程序无法打开或文件损坏的处理方法(2)

1. 检查状态 在sip系统完整性关闭前&#xff0c;我们先检查是否启用了SIP系统完整性保护。打开终端输入以下命令【csrutil status】并回车&#xff1a; 你会看到以下信息中的一个&#xff0c;用来指示SIP状态。已关闭 disabled: System Integrity Protection status: disabl…

学习 Python 之 Pygame 开发魂斗罗(四)

学习 Python 之 Pygame 开发魂斗罗&#xff08;四&#xff09;继续编写魂斗罗1. 创建子弹类2. 根据玩家方向和状态设置子弹发射的位置(1). 站立向右发射子弹(2). 站立向左发射子弹(3). 站立朝上发射子弹(4). 蹲下发射子弹(5). 向斜方发射子弹(6). 奔跑时发射子弹(7). 跳跃时发射…

ARM uboot 源码分析8 - uboot的环境变量

一、uboot 的环境变量基础 1、环境变量的作用 (1) 让我们可以不用修改 uboot 的源代码&#xff0c;而是通过修改环境变量&#xff0c;来影响 uboot 运行时的一些数据和特性。譬如说&#xff0c;通过修改 bootdelay 环境变量&#xff0c;就可以更改系统开机自动启动时倒数的秒…

【MindSpore】安装和使用MindSpore 2.0.0版本简单实现数据变换Transforms功能

本篇文章主要是讲讲MindSpore的安装以及根据官方提供的例子实现数据变换功能。 昇思MindSpore是一款开源的AI框架&#xff0c;旨在实现易开发、高效执行、全场景覆盖三大目标。 目录1、加入MindSpore社区2、安装前准备2.1、获取安装命令2.2、安装pip2.3、确认系统环境3、安装Mi…

JavaWeb--Web概述

Web概述1 Web概述1.1 Web和JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器1.3 Web核心课程安排今日目标&#xff1a; 了解JavaWeb开发的技术栈 1 Web概述 1.1 Web和JavaWeb的概念 Web是全球广域网&#…

阿里软件测试二面:adb 连接 Android 手机的两种方式,看完你就懂了

前言 随着现在移动端技术的突飞猛进&#xff0c;导致现在市场上&#xff0c;APP 应用数不胜数&#xff0c;那对于测试工程师而言&#xff0c;对于 APP 的测试&#xff0c;那基本就是一个必修课了。 今天&#xff0c;我就来给大家介绍一下&#xff0c;adb 连接 Android 手机的两…

Spring(Bean生命周期)

目录 1. 生命周期简图2. 扩展接口介绍 2.1 Aware接口2.2 BeanPostProcessor接口2.3 InitializingBean2.4 DisposableBean2.5 BeanFactoryPostProcessor接口3. spring的简化配置 3.1 项目搭建3.2 Bean的配置和值注入3.3 AOP的示例 1. 生命周期简图 2. 扩展接口介绍 2.1 Aware接…

Redis实现分页和多条件模糊查询方案

导言 Redis是一个高效的内存数据库&#xff0c;它支持包括String、List、Set、SortedSet和Hash等数据类型的存储&#xff0c;在Redis中通常根据数据的key查询其value值&#xff0c;Redis没有模糊条件查询&#xff0c;在面对一些需要分页、排序以及条件查询的场景时(如评论&…

PolarDB数据库的CSN机制

背景 对postgres数据库熟悉的同学会发现在高并发场景下在获取快照处易出现性能瓶颈&#xff0c;其原因在于PG使用全局数组在共享内存中保存所有事务的状态&#xff0c;在获取快照时需要加锁以保证数据一致性。获取快照时需要持有ProcArraryLock共享锁比遍历ProcArray数组中活跃…

[计算机网络(第八版)]第二章 物理层(学习笔记)

网络层是网络体系结构的最低层&#xff0c;不是具体的传输媒体&#xff0c;也不是连接计算机的具体物理设备 2.1 物理层的概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。物理层的作用&#xff1a; 要尽可能地屏…

面试题:Android 中 Intent 采用了什么设计模式?

答案是采用了原型模式。原型模式的好处在于方便地拷贝某个实例的属性进行使用、又不会对原实例造成影响&#xff0c;其逻辑在于对 Cloneable 接口的实现。 话不多说看下 Intent 的关键源码&#xff1a; // frameworks/base/core/java/android/content/Intent.java public cla…

阅读笔记9——DenseNet

一、DenseNet DenseNet的网络结构如图1-1所示&#xff0c;其核心是Dense Block模块&#xff0c;Dense Block中的一个黑点就代表一个卷积模块&#xff08;不是一个卷积层&#xff0c;而是DenseNet提出的一个BottleNeck模块&#xff0c;后文有讲解&#xff09;&#xff0c;每条黑…

ClassPathResource遇到的坑:class path resource

读取文件--ClassPathResource前言一、使用ClassPathResource.getFile()的坑二、通过流读取文件内容总结前言 需求&#xff1a;拿到一个小程序的皮肤文件夹&#xff0c;放在resource目录下 1:根据皮肤的style.json&#xff0c;获取json内的${xxx.png}变量&#xff08;获的图片名…

「2」指针进阶——详解

&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680; 目录 &#x1f430;指向函数指针数组的指针(很少用&#xff0c;了解) &#x1f430;回调函数&…

【Arduino 无刷电机控制教程】

【Arduino 无刷电机控制教程】 1. 概述2. 试验准备3. 实验原理4. Arduino 无刷电机控制 – 电路图4.1 实验组件4.2 用于 BLDC 电机控制的 Arduino 代码5. 实验验证5.1 电位计控制无刷电机速度5.2 电调校准在本教程中,我们将学习如何使用 Arduino 和 ESC 控制无刷电机。如果您想…

建议将com.alibaba:fastjson升级至1.2.83

问题 升级了gradle&#xff0c;改了文件存储位置&#xff0c;项目需要重新构建下载依赖文件&#xff0c;发现fastjson 1.2.66一直下载不下来一直卡在下载&#xff0c;就想着手动下载下试试&#xff0c;就去了mvnrepository网站找到fastjson时&#xff0c;发现了fastjson2 Note…

一文让你彻底了解Linux内核文件系统

一&#xff0c;文件系统特点 文件系统要有严格的组织形式&#xff0c;使得文件能够以块为单位进行存储。文件系统中也要有索引区&#xff0c;用来方便查找一个文件分成的多个块都存放在了什么位置。如果文件系统中有的文件是热点文件&#xff0c;近期经常被读取和写入&#xf…

数学不好,英语不行,非本专业,可以学IT吗?

很多小伙伴&#xff0c;都会问小青一些比较类似的问题。比如&#xff1a;不是计算机专业的&#xff0c;可以学编程吗&#xff1f;数学一直就不好&#xff0c;可以转行学IT吗&#xff1f;学编程开发&#xff0c;对英语的的要求会不会很高&#xff1f;01计算机不是计算机专业的&a…

C/C++开发,无可避免的内存管理(篇三)-规划好内存

一、用内存空间换效率 1.1 allocatoe类模板 在前面简述模板顺序容器时&#xff0c;就提到过&#xff0c;标准库中的 vector 类是通过预先分配额外内存以换取不不用每次添加元素都要重新分配内存和移动元素&#xff0c;而是将元素直接保存加入的预先分配的内存区域。在预先分配…

【Git】Git冲突与解决方法

目录 一、Git冲突如何产生&#xff1f; 二、解决Git冲突—手动修改冲突 【第一步】在 hot-fix 分支上增加如下代码&#xff0c;并且提交。 【第二步】在master 分支上同样的地方增加如下代码&#xff0c;并且提交。 【第三步】 我们现在在 master 分支上合并 hot-fix 分支&a…