数据结构学习分享之顺序表详解

news2024/11/25 2:52:03

数据结构第二课

  • 1. 前言
  • 2. 线性表
  • 3. 顺序表
    • 3.1 概念以及结构
      • 3.11 静态顺序表
      • 3.12 动态顺序表
  • 4. 顺序表的实现
    • 4.1 顺序表内容的命名
    • 4.2 初始化结构
    • 4.3 初始化函数
    • 4.4 扩容函数
    • 4.5 尾插函数
    • 4.6 打印函数
    • 4.7 尾删函数
    • 4.8 头插函数
    • 4.9 头删函数
    • 4.10 销毁顺序表
  • 5. 写代码时应该注意的点
  • 6. 顺序表问题思考以及题目推荐
  • 7. 总结


1. 前言

在前一个章节中我们介绍到, 数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。 那么具体有哪些结构是我们常常用来存储数据的呢?今天就给大家讲解其中的一个结构:顺序表, 本篇文章将收录于数据结构学习分享专栏,有兴趣关于数据结构知识的可以点点订阅,持续更新中ing~~.

想看顺序表所有代码的同学,我的码云放在了最后


2. 线性表

在我们认识顺序表之前,我们先来了解一些线性表的概念:

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

我们要讲的顺序表本质上就是一个数组,通过数组形式来存储数据的结构,但是在数组的基础上,它要求数据从头开始存,并且是连续存储,不能跳跃间隔



3. 顺序表

3.1 概念以及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况采用数组存储。在数组上完成数据的增删查改。 我们说其实所有的数据结构都离不开增删查改,因为它的作用是用来存储数据,就得满足这几个需求

我们通常还讲顺序表分为两种:

  • 静态顺序表

  • 动态顺序表

3.11 静态顺序表

静态顺序表就是使用定长数组来存储数据,我们通常在写静态顺序表的时候将数组大小用define定义宏的形式定义在开
但是静态顺序表有很大的缺陷,

  • 那就是我不知道我要存储的数据到底有多大,假如我的静态空间为1000,但是我只存储了100个数据,这样的空间浪费是很可怕的
  • . 又比如我们想存500个数据,但是我们的静态空间只有400个,这也很苦恼.所以我们这一节只给大家实现动态顺序表,使用动态顺序表来弥补这种缺陷

3.12 动态顺序表

顾名思义就是通过动态开辟空间来存储数据的顺序表,这里我们可以使用malloc,realloc等函数,实现它数据多了我们就扩容这种功能


4. 顺序表的实现

4.1 顺序表内容的命名

我们之前说我们存储数据的结构要实现增删查改这些功能,这里我们给出了很几个函数来实现这种功能,分别是: 尾插(在末尾插入数据),尾删(删除末尾的数据),头插(从开头插入数据),头删(删除开头的数据),还有一个销毁顺序表的函数和初始化函数 ,我们将这些名字统一命名为:

  • SeqListPushBack(尾插)

  • SeqListPopBack(尾删)

  • SeqListPushFront(头插)

  • SeqListPopFront(头删)

  • SeqListDestory(销毁顺序表)

  • SeqListInit(初始化链表)

这里的前缀Seqlist就是顺序表的意思,Push就是插入,Pop就是删除.我们这样命名这些函数的意义有两个,一是别人可以一眼看出我们写的函数是为了实现什么功能,增强了代码的可读性,二是这些函数名的出处来自于C++的STL库,在我们之后的C++学习中会遇见这些名字,所以我们跟着这个STL库来取名字是没有问题的
_
如果你想取别的名字,比如a,b,c啥的也是没问题的,你自己能看懂就行,但是我不推荐这种做法,我们的代码风格也是很重要的.


4.2 初始化结构

像这种比较正式的代码我们都不会在一个.c文件中完成.在我们编写代码之前,我们需要创建三个文件,分别是:

  • test.c文件—用来测试你写的顺序表能不能用

  • SeqList.c文件—函数的代码实现

  • SeqList.h文件—函数的声明和库函数的包含


我们先来定义一个结构体:

struct SeqList
{
	int* a;//创建的数组用来存储数据
	int size;//size代表数组中存储了多少个有效数据
	int capacity;//表示数组实际能存储的空间有多大
};

我们会发现,我们的结构体中的数组定义用的是int类型,那假如我们想存储char类型或者float类型的数据我们就要把我们所有代码中的int全都改了,所以我们这个地方借助typedef来重命名一下我们的类型,并且我们还可以将我们的结构体一块重命名了,修改代码后:

#pragma once//在.h文件中操作
#include<stdio.h>//包含常见的头文件
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
typedef int SLDataType;//想存什么类型就把int改成什么
//
typedef struct SeqList
{
	SLDataType* a;
	int size;//size代表数组中存储了多少个有效数据
	int capacity;//表示数组实际能存储的空间有多大
}SL;//将struct SeqList重命名为SL,方便后面写代码


4.3 初始化函数

我们定义了结构体后没有将结构体初始化,所以我们写一个函数来将它初始化一下:(如果你不理解我们要传地址,别慌,后面会讲)

void SeqListInit(SL* ps)//初始化
{
	ps->a = NULL;//这里代表数组里面最开始什么都不放
	ps->size = ps->capacity = 0;//容量和空间都为0
}

写完这个函数后,我们就可以在test,c文件中先调用一下它,看看有没有问题

#include"SeqList.h"//包含头文件
void TestSeqList1()//这里我们在test.c文件中创建了一个测试函数,它的目的是为了测试我们写的SeqList.c文件中的代码是否正确
{
	SL s1;//定义一个结构体(SL为重命名前的struct SeqList)
	SeqListInit(&s1);
}
int main()
{
	TestSeqList1();
	//TestSeqList2();
	return 0;
}


4.4 扩容函数

我们在初始化结构体时,将数组的长度设置为空,代表它是没有空间的,所以我们在进行插入数据操作之前,应该先判断我们数组中有没有空间?空间够不够?所以我们就引出了扩容,我们把扩容这个功能单独写成一个函数,当然你也可以在尾插函数或者头插函数中判断是否需要扩容,并且将扩容函数一起写到尾插/头插中,我们先上代码再解释:
(如果你不理解为什么要传结构体变量的地址,别慌,后面会讲)

void SeqListCheckCapacity(SL* ps) //判断是否需要扩容
{
	if (ps->size == ps->capacity)//两种情况,一种为顺序表没有空间,一种为空间不够(capacity已经满了)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//当空间为0时给四个空间,不为0时将原先空间乘2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//当我们的数组a的空间为0时,realloc的功能相当于malloc
		if (tmp == NULL)//判断动态开辟空间是否成功,如果不成功就退出程序
		{
			exit(-1);
		}
		ps->a = tmp;//将数组a指向我们开辟成功的这份空间
		ps->capacity = newcapacity;//将数组的容量赋值为新的容量
	}
}

当我们的size等于capacity时,也就是数组存储有效数据个数和空间容量大小相同时,有两种情况,一是size和capacity都为0,也就是没有空间,还有一种就是空间被存储满了. 所以这个地方我们用了三目运算符来判断是哪一种情况,如果是没有空间的情况,那我们先给它4个空间(当然如果你愿意你也可以给他8个,6个,看你的意愿),如果是空间已经满了的情况,那我们就将它的空间给扩容成原来的两倍(当然如果你愿意你也可以扩容为原来的三倍,也是看你自己的意愿).最后将我们的数组指针a指向我们刚刚开辟的这份空间.



4.5 尾插函数

在我们初始化之后,就可以开始我们的尾插了,先上代码再解释:
( (如果你不理解为什么要传结构体变量的地址,别慌,后面会讲) )

void SeqListPushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps!=NULL);//传进来的ps结构体不能为空
	SeqListCheckCapacity(ps);//这个地方我们只需要一级指针,传ps而不是&ps,&ps是二级指针了
	ps->a[ps->size] = x;//size位置刚好是数组最后一个元素的后一个位置
	ps->size++;//尾插后讲有效数据加1
}

在我们尾插之前,我们一定要判断我们的数组存储的数据是不是已经满了(甚至是不是没有空间)
我们现在可以在test.c中测试一下我们写的尾插函数有没有问题:

#include"SeqList.h"
void TestSeqList1()
{
	SL s1;//定义一个结构体
	SeqListInit(&s1);
	SeqListPushBack(&s1,1);//尾插1 2 3 4
	SeqListPushBack(&s1,2);
	SeqListPushBack(&s1,3);
	SeqListPushBack(&s1,4);
}
int main()
{
	TestSeqList1();
    return 0;
}

写完后如果你想看你是否插入成功了,我们可以取调试窗口查看,但是我觉得这样每次都去看调试很麻烦,所以我写一个打印函数,可以讲数组的内容打印出来



4.6 打印函数

void SeqListPrint(SL* ps)//打印
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

写完打印函数后我们再去验证一下上面写的尾插:

#include"SeqList.h"
void TestSeqList1()
{
	SL s1;//定义一个结构体
	SeqListInit(&s1);
	SeqListPushBack(&s1,1);//尾插1 2 3 4
	SeqListPushBack(&s1,2);
	SeqListPushBack(&s1,3);
	SeqListPushBack(&s1,4);
	SeqListPrint(&s1);
}
int main()
{
	TestSeqList1();
    return 0;
}

我们的1 2 3 4 就被打印出来了:

在这里插入图片描述



4.7 尾删函数

先上代码再解释:

void SeqListPopBack(SL* ps)//尾删
{
	assert(ps);//ps不能为null
	assert(ps->size > 0);//size必须大于0,不然是没有数据可以删除的,并且size--后,
	                     //size会等于负数,下次再使用ps->a[size]时会报错
	ps->size--;//直接将size减1,我们的数组就访问不到最后一个数据了
}

这里大家可以自己去测试一下我们的尾删代码:
在这里插入图片描述



4.8 头插函数

void SeqListPushFront(SL* ps, SLDataType x)//头插
{
	assert(ps);
	SeqListCheckCapacity(ps);//判断是否需要扩容
	int end = ps->size-1;//将end指向数组的最后一个元素
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];//将前面的数据往后挪动,而且必须是从最后一个数据开始挪
		end--;
	}
	ps->a[0] = x;//将插入的数据放在第一个位置
	ps->size++;//将有效数据加1
}

这里我还是说明一下为什么我们挪动数据的时候要从后面开始挪动:

在这里插入图片描述
这里大家可以用test.c文件去测试一下:



4.9 头删函数

和尾删一样,头删之前也要判断我们的顺序表是否为空.

void SeqListPopFront(SL* ps)//头删
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 1; i < ps->size; i++)
	{
		ps->a[i - 1] = ps->a[i];//将后面的数据往前挪动,并且要从最开头开始挪动,原因和我们之前的头插是一样的
	}
	ps->size--;//挪动完后将size减1
}


4.10 销毁顺序表

当我们使用完顺序表表后,需要将它销毁掉,并且下次使用时再重新初始化:

void SeqListDestory(SL* ps)//销毁顺序表
{
	free(ps->a);//把a指向的空间释放掉
	ps->a = NULL;//将指针a置空
	ps->size = 0;//将size和capacity都置空
	ps->capacity = 0;
}


5. 写代码时应该注意的点

  • . 我们在写尾插,尾删,头插,头删…时,我们将结构体的地址传过去,并且用指向接受,这时因为我们需要改变结构体里面的内容,和我们之前讲过的传值调用和传址调用一样,但是我们的打印函数其实是可以不传地址过去的,因为它不改变原来的内容只是实现一个打印功能,但是为了这个地方函数声明的统一美观,所以我这里也传了地址过去,当然你们也可以不传地址

  • 我们在实现头插和尾插的时候,首先要判断顺序表的空间足不足够,或者有没有空间,而在我们实现头删和尾删的时候,首先要判断顺序表中还有没有内容给我们删除,如果你不做判断,多删了内容,可能这时是不会报错的,但是你在销毁顺序表时会遇见错误

  • 写代码时应该注意我们的命名风格,因为我们的代码不仅仅是给自己看的,更重要的是别人能够读懂!如果命名风格不好,甚至你自己过了段时间你都不知道自己写的是什么意思.


6. 顺序表问题思考以及题目推荐

问题:
1. 顺序表的中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们
再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢? 我们下一章将会为大家揭晓!


相关题目:

  • 原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。OJ题链接
  • 删除排序数组中的重复项。OJ题链接
  • 合并两个有序数组。OJ题链接


7. 总结

本章给大家讲解完了数据结构中最简单的结构—顺序表的增删查改,其实我们的顺序表还可以实现查找特定位置的数据,
删除特定位置的数据的功能,这里我只给出了我们最简单的功能的实现,有兴趣的同学可以去我的码云看看所有的代码.

下章预告:单链表

我的码云:gitee:杭电码农

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

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

相关文章

安装python以及编辑器pycharm

文章目录 前言一、安装python1.安装python2.测试python3.查看环境变量 二、安装Pycharm1.下载Pycharm2.查看环境变量3.测试 总结 前言 python是重要的程序语言之一 本文介绍如何安装python&#xff0c;以及如何安装python编辑器----Pycharm 一、安装python 1.安装python 首先…

SpringCloud_OpenFeign服务调用和Resilience4J断路器

文章目录 一、负载均衡概论1、服务器负载均衡2、客户端负载均衡3、客户端负载均衡策略(SpringCloudRibbon)4、客户端负载均衡策略(SpringCloudLoadBalancer) 二、SpringCloudOpenFeign服务调用1、OpenFeign服务调用的使用2、OpenFeign服务调用的日志增强3、OpenFeign服务调用超…

下载——安装——使用FinalShell

下载——安装——使用FinalShell FinalShell简介&#xff1a;下载&#xff1a;使用&#xff1a; FinalShell简介&#xff1a; FinalShell是一款免费的国产的集SSH工具、服务器管理、远程桌面加速的软件&#xff0c;同时支持Windows&#xff0c;macOS&#xff0c;Linux&#xf…

总结吴恩达 ChatGPT Prompt 免费课程

吴恩达联合 OpenAI 官方&#xff0c;发布了免费的 ChatGPT Prompt 视频教程。 链接&#xff1a;https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/2/guidelines 视频纯英文&#xff0c;小姐姐的英伦腔&#xff0c;听得很舒服。 我看了第一集&#xff0c;讲了四个技巧&…

阿里云国际版ACE与国内版ACE区别

1.国际版ACE与国内版ACE有哪些不同 2.国际版ACP/ACE约考流程 2.1 登录VUE官方网站约考 https://www.pearsonvue.com.cn/Clients/Alibaba-Cloud-Certification.aspx ​ 2.2 如果之前有注册过账户&#xff0c;那就直接登录&#xff0c;如果还没有账户&#xff0c;那就创建账户 2.…

React之动态渲染菜单(无状态机)

首先如果需要动态渲染菜单&#xff0c;并且根据不同用户获取不同的菜单&#xff0c;需要下面步骤&#xff1a; 我们动态渲染菜单需要使用递归的方式 1️⃣&#xff1a;首先用户的菜单都是从后端获取的&#xff0c;所以需要先发送请求&#xff0c;拿到菜单&#xff0c;然后将数…

AI 命名之羊驼

转眼进入 AI 时代&#xff0c;ChatGPT 吹起了一股大语言模型之风&#xff0c;恐怕羊驼们绝不曾想到&#xff0c;自己的种族竟也被卷入其中。 AI 产品的命名一向偏好晦涩的缩写。GPT&#xff08;Generative Pre-trained Transformers&#xff09;已经是最简明直白的一类。相比之…

gRPC入门教程

1.简介 gRPC是Google开发的一个跨平台、开源的远程过程调用(RPC)框架&#xff0c;可以使用Protocol Buffers作为接口定义语言(IDL)和底层消息交换格式。 在gRPC中&#xff0c;客户端应用程序可以直接调用位于不同机器上的服务器应用程序的方法&#xff0c;就像本地对象一样&a…

【Java笔试强训 2】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;排序子…

Linux-03目录管理

注意&#xff1a; 在下面的讲解中&#xff0c;每个命令都有很多的参数说明&#xff08;选项&#xff09;&#xff0c;我们只讲其中的几个&#xff0c;关键是让学生掌握命令的语法&#xff1b;学生学习完语法后&#xff0c;就可以自己按照参数书写各种命令&#xff0c;这也是我们…

UEngine运行器2.0.1——修复部分问题

介绍 新版本Deepin/UOS发布后&#xff0c;可以在应用商店安装部分官方已适配的安卓应用&#xff0c;对爱好者来说&#xff0c;不能自己安装APK软件包始终差点意思&#xff0c;本程序可以为Deepin/UOS上的UEngine安卓运行环境安装自定义APK软件包&#xff0c;并能发送安装的APK包…

语义分割学习笔记(五)U-net网络

推荐课程&#xff1a;U-Net网络结构讲解(语义分割)_哔哩哔哩_bilibili 感谢博主霹雳吧啦Wz 提供视频讲解和源码支持&#xff0c;真乃神人也&#xff01; 目录 1. U-net网络模型 2. 分割效果 3. U-Net源码解析(Pytorch版) 4. 测试结果 1. U-net网络模型 U-Net网络由两部分…

【Leetcode -147.对链表进行插入排序 -237.删除链表中的节点】

Leetcode Leetcode -147.对链表进行插入排序Leetcode - 237.删除链表中的节点 Leetcode -147.对链表进行插入排序 题目: 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤 : 插入排序是迭代的&…

设计模式-三种工厂模式的优缺点和使用场景

本文参考&#xff1a; 常见设计模式解析,应用场景以及优点(一)单例,工厂,抽象工厂,构造者_口怪物口的博客-CSDN博客_简述常见的工厂模式以及单例模式的使用场景 轻松理解工厂模式&#xff01;就等面试官问了&#xff01;_牛客网 (nowcoder.com) 工厂模式 工厂模式&#xff08;…

4.29补题记录

昨天做了俩道补题&#xff0c;感觉很自己有点无脑了 一、镜像最短距离 思路 首先这道题我最开始想的是bfs记录步数来做的&#xff0c;当时测试的时候没写出来&#xff0c;后来&#xff0c;看补题的时候&#xff0c;才发现这个就是简单的数学问题&#xff0c;我们只要计算出初…

项目管理软件可以用来做什么?这篇文章说清楚了

项目管理软件是用来干嘛的&#xff0c;就得看对项目的理解。项目是为创造独特的产品、服务或成果而进行的临时性工作。建造一座大楼可以是一个项目&#xff0c;进行一次旅游活动、日常办公活动、期末考试复习等也都可以看成一个项目。 项目管理不善会导致项目超时、超支、返工、…

javascript-算法基础-01

时间复杂度 O(1) O(n) O(n) O(2ⁿ) 记得有次面试, 让我求1 … n, 我说用for循环. 当时竟然都忘了等差数列公式了… 一个简单的求和 let res 0 // 1 for(let i; i < arr.length; i){ // 1res arr[i] // n }let res 0 for(const …

肝一肝设计模式【四】-- 建造者模式

系列文章目录 肝一肝设计模式【一】-- 单例模式 传送门 肝一肝设计模式【二】-- 工厂模式 传送门 肝一肝设计模式【三】-- 原型模式 传送门 肝一肝设计模式【四】-- 建造者模式 传送门 文章目录 系列文章目录前言一、什么是建造者模式二、举个栗子三、静态内部类写法四、开源框…

Golang-常见数据结构Map

Map map 是一种特殊的数据结构&#xff1a;一种元素对&#xff08;pair&#xff09;的无序集合&#xff0c;pair 的一个元素是 key&#xff0c;对应的另一个元素是 value&#xff0c;所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构&#xff1a;给定 key&…

5 款 AI 老照片修复工具的横向比较

在大语言模型和各类 AI 应用日新月异的今天&#xff0c;我终于下定决心&#xff0c;趁着老照片们还没有完全发黄褪色、受潮粘连抑或损坏遗失&#xff0c;将上一代人实体相册里的纸质胶卷照片全部数字化&#xff0c;并进行一次彻底的 AI 修复&#xff0c;好让这些珍贵的记忆能更…