顺序表详解

目录

  • 线性表
  • 顺序表
    • 概念及结构
    • 接口实现
      • 初始化函数void SLInit(SL *psl);
      • 销毁函数 void SLDestroy(SL *psl);
      • 尾插函数void SLPushBack(SL* psl ,SLDataType x);
      • 封装函数void SLCheckCapacity(SL* psl)
      • 头插函数void SLPushFront(SL* psl, SLDataType x);
      • 尾删函数void SLPopBack(SL* psl);
      • 头删函数void SLPopFront(SL* psl);
      • 任意下标插入函数void SLInsert(SL* psl, int pos, SLDataType x)
      • 任意下标删除函数void SLErase(SL* psl, int pos)
      • 查找数据函数int SLFind(SL* psl, int pos, SLDataType x)
  • 数组越界的情况分析

感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒 个人主页
🥸🥸🥸 C语言
🐿️🐿️🐿️ C语言例题
🐣🐣🐣 python
🐓🐓🐓 数据结构C语言

下面是这篇文章会涉及到的知识,如果在阅读的过程中有疑惑的可以看一下
C语言函数介绍(详解)
C语言数组介绍(详解)
C语言深入理解指针(非常详细)(一)
动态内存管理(上)
动态内存管理(下)
自定义类型结构体(上)
自定义类型结构体(中)
自定义类型结构体(下)

线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列
常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线
但是在物理结构上并不一定是连续的
线性表在物理上存储时,通常以数组和链式结构的形式存储
在这里插入图片描述

顺序表

概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存

在数组上完成数据的增删查改
顺序表和数组的区别:
1:顺序表的存储必须是连续的,而数组是可以间隔的存储
2:顺序表在数组中存储时必须是从头开始依次存储,而数组可以在任意位置存储

顺序表一般可以分为:
静态顺序表:使用定长数组存储元素
缺点:N不知道给多少,可能会造成N太大或者太小

#define N 7
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType array[N];
	size_t size;
} SeqList;

在这里插入图片描述

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

typedef struct SeqList
{
	SLDataType* arry;
	size_t size;
	size_t capacity;
}SeqList;

在这里插入图片描述

接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用

所以现实中基本都是使用动态顺序表,根据需要动态的分配空间
大小,所以下面我们实现动态顺序表

首先我们先创建3个文件
1:SeqList.c是放一些功能函数具体实现的文件
2:SeqList.h是放一些功能函数函数名的文件
3:test.c是测试我们实现出的顺序表功能是否成功
之后就是实现函数了

初始化函数void SLInit(SL *psl);

SeqList.c文件
void SlInit(SL psl)
{
	psl.a = NULL;
	/*psl.size=....;
	psl.capacity=....;*/
}

这里有一个常见的错误,就是我们在test.c中用初始化函数时往往会这样传

test.c文件
void TestSL1()
{
	SL psl;
	SLInit(psl);
}

因为我们的目的是为了初始化,所以在传参的时候我们要将地址传入函数
如果不传的话就相当于我们将顺序表的成员都拷贝了一遍,然后对拷贝到成员进行初始化,当出了这个初始化函数的时候拷贝到成员就会销毁

简单的来说就是没传入地址就是形参,而形参是实参的拷贝,出了作用域就会自动销毁,只有传入地址才可以更改实参
(如果形参和实参不是很清楚的可以看一下我上面链接中的 C语言函数介绍(详解))

所以正确的传入方式为

SeqList.c文件
void SlInit(SL *psl)
{
    assert(psl);//防止有人传空指针
	psl ->a = NULL;//结构体用箭头解引用
	psl->size=0;
	psl->capacity=0;
}
test.c文件
void TestSL1()
{
	SL s1;
	SLInit(&s1);
}

销毁函数 void SLDestroy(SL *psl);

SeqList.c文件
void SLDestroy(SL* psl)
{
    assert(psl);//防止有人传空指针
	if (psl->a!=NULL)
	{
		free(psl->a);//因为a是指针,所以要free a指向的空间
		psl->a = NULL;//然后让a=NULL,不能让a指向被销毁的空间
		psl->size = 0;
		psl->capacity = 0;
	}
}

尾插函数void SLPushBack(SL* psl ,SLDataType x);

尾插函数我们需要分情况讨论
1:尾插时有剩余的空间
2:尾插时没有剩余的空间

SeqList.c文件
void SLPushBack(SL* psl, SLDataType x)
{
    assert(psl);//防止有人传空指针
	if ((psl->size) >= (psl->capacity))
	{
		int newCapacity = psl->capacity * 2;
		SLDataType* a = realloc(psl->a, sizeof(SLDataType) * newCapacity);
		if (a == NULL)
		{
			perror("realloc fail");
			return;
		}
		psl->capacity = newCapacity;
	}
	psl->a[psl->size] = x;
	psl->size++;
}

这段代码的逻辑为判断有效长度是否大于总的长度,如果大于就意味着需要扩容

于是用newCapacity 等于2倍的capacity来扩大总长度(注意这里的2倍扩容并不一定非要2倍

然后用realloc对空间扩容,因为realloc的函数特性(如果realloc不是很清楚可以看这篇文章最上面的链接内存函数部分),所以将realloc返回到地址给a

之后让capacity=newCapacity

然后对数组末尾进行插入,再将有效长度size加1

但是这段代码其实有几处的问题
1:在之前初始化函数中我们对于capacity的初始化为0,而这里的newCapacity是直接等于capacity*2,也就是newcapacity也为0

解决措施是在之前的初始化部分将capacity赋值为大于0的数
或者我们通过三目运算
进行判断赋值

2:我们这段代码是将realloc返回的地址用a来接受,但是如果返回失败的话,a也就找不到之前的地址了

解决措施是我们用一个新的指针tmp来接受realloc返回到地址,然后判断是否扩容成功,成功的话就a=tmp

可能会有人认为a在之前初始化函数中a是空指针,而realloc(psl->a, sizeof(SLDataType) * newCapacity)中我们是对a进行扩容,但是a既然是空指针,我们就没有办法扩容,其实realloc是可以扩容的,因为当a是空指针的时候,realloc的功能就和malloc的功能一样了

封装函数void SLCheckCapacity(SL* psl)

因为对于后面的函数和尾插函数SLPushBack有一些代码是重合的,所以为了少写一些代码,我们将这些重合的写入这个封装函数SLCheckCapacity中
封装函数代码如下:

SeqList.c文件
void SLCheckCapacity(SL* psl)
{ 
    assert(psl);//防止有人传空指针
	if ((psl->size) >= (psl->capacity))
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = realloc(psl->a, sizeof(SLDataType) * newCapacity);//sizeof(SLDataType)一定要乘,否则会出现越界
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		psl->a = tmp;
		psl->capacity = newCapacity;
}

头插函数void SLPushFront(SL* psl, SLDataType x);

在这里插入图片描述
对于头插函数我们并不能向前扩容,所以我们只能将数据向后移动

于是我们需要借助一个end指向顺序表最后的一块有效数据,end=size-1

然后通过a[end+1]=a[end],end–,使所有数据向后移动

最后再对a[0]进行头插

但是需要注意的是这里头插一次的时间复杂度为O[N],因此不能经常使用头插

SeqList.c文件
void SLPushFront(SL * psl, SLDataType x)
{    
    assert(psl);//防止有人传空指针
	SLCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
	psl->a[0] = x;
	psl->size++;
}

尾删函数void SLPopBack(SL* psl);

SeqList.c文件
void SLPopBack(SL* psl)
{
    assert(psl);//防止有人传空指针
	psl->size--;
}

在这里插入图片描述

尾插函数只需要将记录有效数据的size减减就行了(也就是图中的红线),因为之后用顺序表时可能会用头插或者尾插,使图中的4被其他数字覆盖

但是这个函数也是有一点问题的,假如我们一直删除数据,当数据删除完了我们仍然不停止,就会出问题

当数据删除完后我们仍然继续删除,那么size就会为负数,而当我们再进行插入数据的时候,就会出现数据的丢失,原因就是size为负数,我们插入的数据不在我们想要的空间里

为了防止这种情况的出现,我们需要加一个断言

void SLPopBack(SL* psl)
{
    assert(psl);//防止有人传空指针 
    assert(psl->size>0);
	psl->size--;
}

但是我觉得这种尾删方法是有一点问题的,因为4的空间没有办法消除,所以a[size+1]仍然可以访问到4,这是不好的,但是也不知道该怎么去解决

头删函数void SLPopFront(SL* psl);

头删函数和之前的头插函数思路是一样的,把后面的数据往前移,将原来的数据覆盖掉就可以了

但是这里也有一个细节,如果是从后往前挪动数据的话,是无法实现头删的
在这里插入图片描述
比如上图中,我们将4往前挪动覆盖3,然后end–,但是下一次覆盖的话3就找不到了,只能用4把前面的2也给覆盖掉,这不是我们想要的结果

所有我们选择从前开始覆盖

在这里插入图片描述
通过a[end[=a[end+1]的方式挪动数据,然后end++,由于挪动后,end指向的新数据并没有被覆盖,所以就可以继续按照这种方式继续挪动

只不过我们还需要注意挪动结束的条件判断,如果end<size-1后我们就该停止挪动了,然后size–

为什么是end<size-1呢?

因为end是这个数组的下标,size-1就代表数组的最后一个有效数据的下标,如果是end<size的话就会访问到没有有效数据的空间

	void SLPopFront(SL* psl)
	{      
	        assert(psl);//防止有人传空指针
		    assert(psl->size > 0);
			int end = 0;
			while (end < psl->size-1)
			{
				psl->a[end] = psl->a[end + 1];
				end++;
			}
			psl->size--;
	}

任意下标插入函数void SLInsert(SL* psl, int pos, SLDataType x)

	void SLInsert(SL* psl, int pos, SLDataType x)
	{
		assert(psl&&(pos>=0)&&(pos<=psl->size));
		SLCheckCapacity(&psl);
		int end = psl->size - 1;
		while (end >= pos)
		{
			psl->a[end + 1] = psl->a[end];
			--end;
		}
		psl->a[pos] = x;
		psl->size++;
	}

注意这里的pos为下标,而assert断言是判断传入的是否为空指针

并且pos下标必须要大于0,而pos小于等于size中pos是可以等于size的,这样就相当于尾插函数

而为什么pos不能大于size呢?

其实是因为顺序表必须是连续的,如果大于size就不会连续了

任意下标删除函数void SLErase(SL* psl, int pos)

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

查找数据函数int SLFind(SL* psl, int pos, SLDataType x)

这个函数的功能是从pos位置的下标开始找数据,如果找到想要的数据就返回对应的下标

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

这个函数可以和删除函数void SLErase(SL psl, int pos)一起使用*

有时候我们并不知道我们想要删除的数据在哪个位置

我们就可以用int SLFind(SL psl, int pos, SLDataType x)去查找,找到想要删除的数据,我们就通过删除函数去删除*

int pos = SLFind(&sl, 0, 1);
	if (pos != -1)
	{
		SLErase(&sl, pos);
	}

数组越界的情况分析

对于一个数组,我们可能会常常发生数组越界访问的错误,但是越界不一定会报错
在这里插入图片描述

在我们越界读取数据的时候并没有报错

而当我们越界写的时候就会报错
在这里插入图片描述

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

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

相关文章

洛谷_P2437 蜜蜂路线_python写法_高精度加法

目录 1. 40分代码 2.高精度加法 3.全AC代码 4.惊掉下巴的解法 P2437 蜜蜂路线 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 1. 40分代码 m, n map(int,input().split())ans 0 d [1,2] flag [0 for _ in range(n1)] def fun(step):global ansif step n:ans 1return…

了解微信小程序开发流程

前言&#xff1a;本文只适合初学者了解大致开发流程&#xff0c;好让后续学习胸有成竹&#xff0c;有条不紊 1、开发准备 ① 在微信公众平台 (qq.com)完成微信小程序账号注册 ②下载安装微信小程序开发者工具 2、创建项目 新建 新建时需要的appid&#xff0c;在微信公众平…

GeoLite2 geoip数据库下载和使用

GeoLite2 数据库是免费的 IP 地理定位数据库&#xff0c;与MaxMind 的 GeoIP2 数据库相当&#xff0c;但准确度较低 。GeoLite2 国家、城市和 ASN 数据库 每周更新两次&#xff0c;即每周二和周五。GeoLite2 数据还可作为 GeoLite2 Country 和 GeoLite2 City Web 服务中的 Web …

微服务监控:确保分布式系统的可观察性与稳定性

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、前言二、微服务监控的重要性三、关键监控指标四、常用监控工具五、最佳实践六、结论 一、前言 在当前的软件开发领域&a…

Lua环境下载与配置

这里介绍如何下载已经编译好的Lua环境&#xff0c;如何配置Lua环境。 如希望自己从源码编译Lua环境&#xff0c;请自行搜索资料。 第一步&#xff1a;下载编译好的lua环境 打开下面链接&#xff0c;然后根据指引下载。 The Programming Language Luahttps://www.lua.org/hom…

【Python系列】合并配置文件的最佳实践

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

ssm停车场管理系统

点赞收藏关注 → 私信领取本源代码、数据库 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于停车场管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了停…

【微服务】spring状态机模式使用详解

一、前言 在很多系统中&#xff0c;通常会涉及到某个业务需要进行各种状态的切换操作&#xff0c;例如在审批流程场景下&#xff0c;某个审批的向下流转需要依赖于上一个状态的结束&#xff0c;再比如电商购物场景中&#xff0c;一个订单的生命周期往往伴随着不同的状态&#…

基于java+springboot+vue实现的付费自习室管理系统(文末源码+Lw+ppt)23-400

摘 要 付费自习室管理系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户…

JavaSE类和对象

目录 1.面向对象 1.1面向对象的过程 2.类的定义和使用 2.1定义 2.2使用 2.2.1实例化 2.2.2访问类中数据 2.3类和对象说明 3.this引用 4.对象的构造及初始化 4.1初始化对象 4.2构造方法 4.2.1特性 4.3默认初始化 4.4就地初始化 5.封装 5.1概念 ​编辑 5.2访问限定…

ky9250(mpu9250)取得原始数据后通过简易卡尔曼滤波获取角度

我们通过ky9250(mpu9250)取得原始数据后&#xff08;gx,gy,gz,ax,ay,az,mx,my,mz&#xff09;后想通过原始数据解算角度姿态信息(想试验各种算法比如卡尔曼、mahony,Madgwick)&#xff0c;现将使用简易卡尔曼滤波获取姿态角度roll,pitch,yaw的方法介绍如下&#xff1a; 未完 稍…

探索C语言中的联合体和枚举:让处理数据更加得心应手

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;http://t.csdnimg.cn/Oytke 小新的主页&#xff1a;编程版小新-CSDN博客 C语言中有内置类型&#xff0c; 比如&…

vue3封装Element表格

配置表头配置多选配置序号自定义操作列按钮 封装表格 Table.vue <template><el-table:data"tableData"width"100%":maxHeight"maxHeight"v-bind"$attrs"selection-change"handleSelectChange"row-click"hand…

【Python】Scrapy 爬虫(简单了解)

Scrapy项目开发流程 1.创建项目 打开cmd scrapy startproject example 就可以创建一个Scrapy项目。这时&#xff0c;我们找到并打开这个文件。 复制路径&#xff1a;C:\Users\25194\example 复制到pycharm里面&#xff0c;打开该项目。 文件介绍 scrapy.cfg setting表明项…

竞赛课第四周(八数码问题+八皇后问题)

目的&#xff1a; 1. 掌握递归和排序 2. 掌握BFS与队列 3. 掌握DFS和递归 4. 熟悉并理解回溯问题 实验内容&#xff1a; 1.八数码问题&#xff1a; 在一个33的棋盘上&#xff0c;放置编号为1~8的8个方块&#xff0c;每个占一格&#xff0c;另外还有一个空格。与空格相邻…

基于ssm的平面设计课程在线学习平台系统(java项目+文档+源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的平面设计课程在线学习平台系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 前台功能&#xf…

(南京观海微电子)——DDIC显示触控芯片介绍

显示驱动芯片&#xff08;Display Driver Integrated Circuit&#xff0c;简称DDIC&#xff09;的主要功能是控制OLED显示面板。它需要配合OLED显示屏实现轻薄、弹性和可折叠&#xff0c;并提供广色域和高保真的显示信号。同时&#xff0c;OLED要求实现比LCD更低的功耗&#xf…

【保姆级教程】YOLOv3图像目标检测:训练自己的数据集

一、YOLOv3图像目标检测原理 二、YOLOv3代码及预训练权重下载 2.1 下载yolov3代码 这里使用的是B站大佬Bubbliiiing复现的yolov3代码 仓库地址&#xff1a; https://github.com/bubbliiiing/yolo3-pytorch 2.2 下载模型预训练权重unet_resnet_medical.pth 链接&#xff1a…

网络基础(二)——HTTP协议

目录 1、2个简单的预备知识 2、HTTP请求和响应的格式 3、实现一个最简单的httpserver 4、HTTP的细节字段 4.1、GET和POST 4.2、HTTP的状态码 4.3、HTTP常见Header 1、2个简单的预备知识 首先我们来看一个域名&#xff1a;http://www.baidu.com/&#xff0c;很明显这是百…

实验三智能手机互联网程序设计(微信程序方向)实验报告

实验目的和要求 请编写下方商品列表页面&#xff0c;展示商品名称和价格&#xff1b; 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; Index.WXML <view class"shop" wx:for"{{10}}"> <vie…