数据结构(一)------顺序表

news2024/11/18 15:38:27

文章目录

  • 前言
  • 一、什么是顺序表
  • 二、实现顺序表
  • 1.静态顺序表
  • 2.动态顺序表

  • 总结


前言

制作不易!三连支持一下呗!!!

从今天起我们将会进入数据结构的学习!

我们先来了解

什么是数据结构

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。

一般数据结构有很多种,一般来说,按照数据的逻辑结构对其进行简单的分类,包括线性结构和非线性结构两类。

线性表在逻辑结构上是连续的,但在物理结构上不一定是连续的!

今天我们就来学习一下线性表之一的顺序表!


一、什么是顺序表

顺序表是最简单的一种数据结构,它底层采用的是我们之前学过的数组,它在逻辑结构(数据之间的关系)上是连续的,在物理结构上也是连续的(因为数组中的数据在内存中是连续存放的)。

但是顺序表又不能等同于数组,它其实是在数组的基础上进行封装,通过增,删,查,改等接口来对数据进行操作。

顺序表根据大小是否是固定的又分为

1.静态顺序表

2.动态顺序表

二、顺序表的实现

1.静态顺序表

底层使用的是固定大小的数组

比如:

#include<stdio.h>
typedef int Seqtype;//如果我们后续要更改存储的数据的类型,这样重定义可以方便我们一键替换
#define Seqtype_MAX 100//方便我们后续修改顺序表的大小
typedef struct SeqList
{
	Seqtype arr[100];//存放100个整型类型的数据
	int size;//记录有效数据(也就是已经保存的数据)的个数
}SL;//使结构体类型更加简洁

这里着重解释一下为什么只创建一个数组来存放数据还不够,我们还需要一个size来记录有效数据的个数:

这是因为当我们在后续实现一些增,删,查,改等接口时难免会遍历已保存的数据,这样size就可以方便我们知道每次需要遍历多少个数据。 

由于静态顺序表有大小是固定的这样的弊端,因此我们更加喜欢使用动态顺序表!

二.动态顺序表

想要实现动态顺序表就要用到我们之前介绍过的动态内存管理的方式(malloc,calloc,realloc),通过realloc函数的特点,在我们空间大小不够的情况下扩容。

实现方式如下:

#include<stdio.h>
typedef int Seqtype;//如果我们后续要更改存储的数据的类型,这样重定义可以方便我们一键替换
typedef struct SeqList
{
	Seqtype* arr;
	int capacity;//记录当前顺序表的容量
	int size;//记录有效数据(也就是已经保存的数据)的个数
}SL;//使结构体类型更加简洁

我们通过指针的方式来遍历整个数组,也通过动态开辟的方式来申请空间。

1. 顺序表的初始化与销毁

创建好一个顺序表,,类比创建变量,我们首先要做的第一步就是初始化这个顺序表。我们可以通过一个接口函数来完成这个功能:

<1>.顺序表的初始化
#include<stdio.h>
#include<assert.h>
void SLInit(SL* ps)
{
    assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

这里我们只强调为什么我们要传址调用,而不能传值调用:

我们在这里是想要初始化这个顺序表,也就是要对顺序表中的内容进行修改,如果我们采用传值调用的方式,形参只是实参的临时拷贝,对形参的修改是不会影响实参的,也就是说实参是并没有被初始化的!因此,我们这里传一个地址过去,通过指针的方式来对顺序表进行初始化(修改)。

<2>. 顺序表的销毁

当我们会初始化顺序表后,销毁顺序表其实也大差不差:

#include<stdio.h>
#include<assert.h>
void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

同样的道理我们这里还是要传址调用!

2.扩容 

动态顺序表最大的特点就是当空间不够时,可以自动开辟更多的空间以存放数据。

扩容的原理也有很多,比如:

1.每次扩容一个元素大小的空间

原则就是每当我们空间不足时,使用realloc函数来增加一个元素的空间。

这种方式有它的优点:不会一次扩容过度,造成空间的浪费。

弊端:每次只扩大一个元素的空间,这样会造成需要频繁的扩容,那样就会频繁的调用这个接口函数,造成程序的运行效率低下!

2.每次扩容固定大小的空间

优点:可以做到不会频繁扩容

弊端:可能会导致一次性扩容过度,造成空间的浪费

可以看出前两种方式是两个极端!

3.成倍数的扩容(强烈推荐)

每次扩容为原来的容量的固定倍数的大小的空间,我们通常会使用1.5倍或2倍(倍数不宜过大,会导致空间的浪费)

这种方式平衡了前两种方式的弊端,既不会过度扩容,也不会特别造成空间的浪费,是一种中性的方法。

说完扩容的原则,我们接下来实现扩容的功能:

在后面插入,删除数据的接口中可能会存在空间不足需要扩容的情况,所以我们单独将扩容拿出来,实现一个扩容的接口。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->capacity == ps->size)//判断是否需要扩容
	{
		int newcapacity = ps->capacity == 0 ? 2 : 2*ps->capacity;
		Seqtype* tmp = (Seqtype*)realloc(ps->arr, newcapacity * sizeof(Seqtype));
		//刚开始ps->arr是NULL,realloc会直接开辟新空间,作用相当于malloc
		//注意单位是字节,要*类型的大小
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);//直接终止程序,比return 1更加暴力
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}

这里我们再实现一个打印顺序表的接口,方便我们进行调试观察代码是否有误

void SLPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
}

非常简单!

3.头插和尾插 

<1>.尾插

尾插顾名思义就是在之前的数据的末尾插入新的数据。

当我们插入数据时就会遇到两种情况:

1.空间足够,直接插入

根据上图可以看出ps->size就是我们要插入的位置的下标 

2.空间不足时

这种情况我们需要先扩容,再插入

代码实现如下:

void SLPushBack(SL* ps, Seqtype x)
{
	assert(ps);
	SLCheckCapacity(ps);//判断是否要扩容
	ps->arr[ps->size++] = x;//注意:不用忘了将ps->size++。
}

<2>.头插

将每次要插入的数据放到数组下标为0的起始位置,同样分为两种情况:

1.空间足够

假如我们要插入100

最后应该是将之前的元素往后挪动,再加100插入到头部,最后ps->size++ 

2.空间不足

对于空间不足的情况,同尾插一样,我们需要先扩容再插入。

下面我们实现代码:

void SLPushFront(SL* ps, Seqtype x)
{
	assert(ps);
	SLCheckCapacity(ps);//判断是否要扩容
	int i = 0;
	for (i = ps->size; i >0; i--)//边界条件:ps->arr[1]=ps->arr[0]
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

4.头删和尾删 

<1>.尾删

尾删的思想与尾插相似,也是从之前数据的末尾删掉一个数据(这里无需判断是否空间足够,因为肯定足够!),代码实现十分简单:

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//确保顺序表中有元素,否则不执行
	ps->size--;
}

可能有些老铁会想先将要删除的元素置为0,再将ps->size--。其实这样是不太合理的,因为我们存入的数据也可能为0,这样无法判断0是删除的数据还是插入进来的数据。而且我们也没必要那样做,因为我们直接将 ps->size--,这样我们遍历顺序表时本就无法再查询到被删除的数据了!

<2>.头删

当我们进行一次头删操作,顺序表将变成这样:

可以看出后面的元素整体向前挪动一位,并且ps->size--。 

代码实现如下:

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);//确保顺序表中有元素,否则不执行
	int i = 0;
	for (i = 0; i < ps->size - 1; i++)//边界位置判断:ps->arr[0]=ps->arr[1]
		                              //还有ps->arr[ps->size-2]=ps->arr[ps->size-1]
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;//注意不要丢掉
}

5.指定位置插入和删除 

<1>.指定位置插入

同样的,指定位置插入数据也要先判断是否需要扩容,原理同上,这里不再过多赘述。

因为需要用户自己指定插入的位置,所以我们还需要一个参数pos来确定插入位置的下标。

数据插入后的顺序表应该是这样的:

可以看出,需要将pos位置及以后的数据整体向后挪动1个单位!!!

void SLInsert(SL* ps, Seqtype x,int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//判断pos的合法性
	SLCheckCapacity(ps);//判断是否要扩容
	int i = 0;
	for (i = ps->size - 1; i >= pos;i--)//边界条件判断:ps->arr[pos+1] = ps->arr[pos]
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[pos] = x;
	ps->size ++ ;
}

特别的,pos=ps->size时就相当于尾插,当pos=0时相当于头插!!! 

 <2>.指定位置删除

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//检验pos的合法性
	assert(ps->size);//确保顺序表中有元素
	int i = 0;
	for (i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;//不要忘记
}

 特别的,pos=ps->size-1时就相当于尾删,当pos=0时相当于头删!!! 

6.查找 

思路很简单,就是遍历整个顺序表

int SLFind(SL* ps, Seqtype x)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//找不到,返回-1
}


总结

以上就是本节的所有内容

下面将整个顺序表的所有代码放到这里

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int Seqtype;//如果我们后续要更改存储的数据的类型,这样重定义可以方便我们一键替换
typedef struct SeqList
{
	Seqtype* arr;
	int capacity;//记录当前顺序表的容量
	int size;//记录有效数据(也就是已经保存的数据)的个数
}SL;//使结构体类型更加简洁
void SLInit(SL* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->capacity == ps->size)//判断是否需要扩容
	{
		int newcapacity = ps->capacity == 0 ? 2 : 2*ps->capacity;
		Seqtype* tmp = (Seqtype*)realloc(ps->arr, newcapacity * sizeof(Seqtype));
		//刚开始ps->arr是NULL,realloc会直接开辟新空间,作用相当于malloc
		//注意单位是字节,要*类型的大小
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);//直接终止程序,比return 1更加暴力
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}
void SLPushBack(SL* ps, Seqtype x)
{
	assert(ps);
	SLCheckCapacity(ps);//判断是否要扩容
	ps->arr[ps->size++] = x;//注意:不用忘了将ps->size++。
}
void SLPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
}
void SLPushFront(SL* ps, Seqtype x)
{
	assert(ps);
	SLCheckCapacity(ps);//判断是否要扩容
	int i = 0;
	for (i = ps->size; i >0; i--)//边界条件:ps->arr[1]=ps->arr[0]
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//确保顺序表中有元素,否则不执行
	ps->size--;
}
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);//确保顺序表中有元素,否则不执行
	int i = 0;
	for (i = 0; i < ps->size - 1; i++)//边界位置判断:ps->arr[0]=ps->arr[1]
		                              //还有ps->arr[ps->size-2]=ps->arr[ps->size-1]
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;//注意不要丢掉
}
void SLInsert(SL* ps, Seqtype x,int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//判断pos的合法性
	SLCheckCapacity(ps);//判断是否要扩容
	int i = 0;
	for (i = ps->size - 1; i >= pos;i--)//边界条件判断:ps->arr[pos+1] = ps->arr[pos]
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[pos] = x;
	ps->size ++ ;
}
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//检验pos的合法性
	assert(ps->size);//确保顺序表中有元素
	int i = 0;
	for (i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;//不要忘记
}
int SLFind(SL* ps, Seqtype x)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//找不到,返回-1
}

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

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

相关文章

2023年算法OOA-CNN-BiLSTM-ATTENTION回归预测(matlab)

OOA-CNN-BiLSTM-Attention鲸鱼算法优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕…

go语言函数进阶

1.变量作用域 全局变量 全局变量是定义在函数外部的变量&#xff0c;它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。 package mainimport "fmt"//定义全局变量num var num int64 10func testGlobalVar() {fmt.Printf("num%d\n", num) /…

汽车网络安全dos, someip

汽车Cyber Security入门之DoS 攻防 - 知乎 3、SOME/IP-TP 近年来火热地谈论下一代EE架构和SOA的时候&#xff0c;总离不开SOME/IP这个进程间通讯协议。在许多应用场景中&#xff0c;需要通过UDP传输大型的SOME/IP有效载荷。鉴于在以太网上传输数据包的大小限制&#xff0c;SO…

多维时序 | Matlab实现DBO-BiLSTM蜣螂算法优化双向长短期记忆神经网络多变量时间序列预测

多维时序 | Matlab实现DBO-BiLSTM蜣螂算法优化双向长短期记忆神经网络多变量时间序列预测 目录 多维时序 | Matlab实现DBO-BiLSTM蜣螂算法优化双向长短期记忆神经网络多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现DBO-BiLSTM多变量时间序…

Matlab|【完全复现】基于价值认同的需求侧电能共享分布式交易策略

目录 1 主要内容 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序完全复现《基于价值认同的需求侧电能共享分布式交易策略》&#xff0c;针对电能共享市场的交易机制进行研究&#xff0c;提出了基于价值认同的需求侧电能共享分布式交易策略&#xff0c;旨在降低电力市…

面经基础版案例(路由,请求渲染,传参,组件缓存)

文章目录 1.案例效果分析2.配置一级路由&#xff08;首页&#xff0c;详情&#xff09;3.配置二级路由4.导航高亮效果5.首页的请求渲染6.传参&#xff08;查询参数 $ 动态路由&#xff09;7.详情页渲染8.组件缓存kepp-alive9.总结 1.案例效果分析 2.配置一级路由&#xff08;首…

实战 | OpenCV+OCR实现弧形文字识别实例(详细步骤 + 源码)

导 读 本文主要介绍基于OpenCV+OCR实现弧形文字识别实例,并给详细步骤和代码。源码在文末。 背景介绍 测试图如下,目标是正确识别图中的字符。图片来源: https://www.51halcon.com/forum.php?mod=viewthread&tid=6712 同样,论坛中已经给出了Halcon实现代码,…

web应用课——(第二讲:CSS)

目录 一、实战项目一&#xff1a;Acwing名片 二、实战项目二&#xff1a;Bilibili名片 三、样式定义方式 四、选择器 五、颜色 六、文本 七、字体 八、背景 九、边框 十、元素展示格式 十一、内边距与外边距 十二、盒子模型 十三、位置 十四、浮动 十五、flex布…

老代码为啥如此设计,我是如何解决编译失败的?

周末翻出三年前的杰作“jpy”&#xff0c;专为人工智能开发的dsl语言&#xff0c;不仅编译无法通过&#xff0c;而且一连串的疑问映入眼帘。这…这…这些文件/内容都是干啥的啊&#xff0c;为什么如此设计&#xff1f; 技术千万条&#xff0c;专研第一条&#xff0c;自嗨无笔记…

Lombok的详细教程

什么是lombok Lombok是一个Java库&#xff0c;它通过提供一组注释来简化Java类的开发。使用Lombok&#xff0c;开发人员可以通过在类或字段上添加注释来自动生成通用的方法&#xff0c;如getter、setter、equals、hashCode等。这样可以减少冗余的样板代码&#xff0c;提高开发效…

2024 年最佳 PDF 编辑器榜单:PDF 编辑的首选

您可能经常遇到或使用 PDF 文件 - 它们在现代跨平台世界中无处不在。大多数时候&#xff0c;在查看 PDF 时&#xff0c;您可以使用免费软件来阅读或评论这些文件。但如果您还需要编辑它们怎么办&#xff1f;这就是 PDF 编辑器的用武之地。 最好的 PDF 编辑器允许您编辑、创建、…

Bytebase 签约 Aptive,助力北美商住害虫控制服务领导者构建统一数据库操作平台

在数字化快速发展时代&#xff0c;有效的规范数据库管理对企业安全运营至关重要。近日&#xff0c;数据库 DevOps 团队协同管理工具 Bytebase 签约北美商住害虫控制服务的领导者 Aptive Environmental&#xff0c;旨在全面优化 Aptive Environmental 的数据库操作管理&#xff…

数据结构与算法教程,数据结构C语言版教程!(第六部分、数据结构树,树存储结构详解)四

第六部分、数据结构树&#xff0c;树存储结构详解 数据结构的树存储结构&#xff0c;常用于存储逻辑关系为 "一对多" 的数据。 树存储结构中&#xff0c;最常用的还是二叉树&#xff0c;本章就二叉树的存储结构、二叉树的前序、中序、后序以及层次遍历、线索二叉树、…

第一节课,用户管理--后端初始化,项目调通。二次翻工

一、代码下载 网址&#xff1a; 用户管理第一节课&#xff0c;阿里生成代码包-CSDN博客 二、项目步骤&#xff0c;参考从 网址&#xff1a; 一、第一节课&#xff0c;用户管理--后端初始化&#xff0c;项目调通-CSDN博客 从这里开始跟随 &#xff08;一&#xff09;、跟随…

eclipse用gerrit提交失败

1.gerrit简介 Gerrit&#xff0c;一种免费、开放源代码的代码审查软件&#xff0c;使用网页界面。利用网页浏览器&#xff0c;同一个团队的软件程序员&#xff0c;可以相互审阅彼此修改后的程序代码&#xff0c;决定是否能够提交&#xff0c;退回或者继续修改。它使用Git作为底…

精通Python第13篇—数据之光:Pyecharts旭日图的魔法舞台

文章目录 引言准备工作绘制基本旭日图调整颜色和样式添加交互功能定制标签和标签格式嵌套层级数据高级样式与自定义进阶主题&#xff1a;动态旭日图数据源扩展&#xff1a;外部JSON文件总结 引言 数据可视化在现代编程中扮演着重要的角色&#xff0c;而Pyecharts是Python中一个…

【数据分析】Excel中使用VBA进行宏编程

目录 0 准备工作1 VBA简介1.1 Excel VBA应用程序的构成1.2 事件驱动1.3 宏1.3.1 创建宏1.3.2 宏安全 2 VBA基础2.1 注释2.2 数据类型2.2.1 基本数据类型2.2.2 枚举类型2.2.3 用户自定义数据类型 2.2 变量2.3 常量2.4 运算符2.5 程序结构2.6 过程2.7 函数 3 Excel应用程序开发流…

配置华为交换机环路检测案例

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系&#xff01; 厦门微思网络​​​​​​https://www.xmws.cn 华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-Datacom 思科认证\CCNA\CCNP\CCIE Linux\RHCE…

给你一颗“定心丸”——记一次由线上事故引发的Log4j2日志异步打印优化分析

一、内容提要 自知是人外有人&#xff0c;天外有天&#xff0c;相信对于Log4j2的异步日志打印早有老师或者同学已是熟稔于心&#xff0c;优化配置更是信手拈来&#xff0c;为了防止我在这里啰里八嗦的班门弄斧&#xff0c;我先将谜底在此公布&#xff1a;_log4j2.asyncQueueFu…

【ArcGIS微课1000例】0098:查询河流流经过的格网

本实验讲述,ArcGIS中查询河流流经过的格网,如黄河流经过的格网、县城、乡镇、省份等。 文章目录 一、加载数据二、空间查询三、结果导出四、注意事项一、加载数据 加载实验配套数据0098.rar中的河流(黄河)和格网数据,如下图所示: 接下来,将查询河流流经过的格网有哪些并…