探索数据结构:深入了解顺序表的奥秘


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 什么是顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,它与数组非常类似,但是相比于数组顺序表有一个非常明显的优点——可以动态内存增长空间大小

2. 顺序表的功能

顺序表可以大致包含以下几个功能:

  1. 初始化顺序表中的数据。

  2. 打印顺序表中的数据。

  3. 对顺序表进行尾插(末尾插入数据)。

  4. 对顺序表中数据进行尾删(末尾删除数据)。

3. 顺序表的定义

定义顺序表我们首先需要一个动态内存开辟的空间,当前数据的个数(size),以及方便扩容的容量大小(capacity)

typedef int SLDataType; //类型重命名,方便后续修改类型
#define N 4 //初始化空间大小
typedef struct SeqList
{SLDataType* a;    //指向动态开辟的数组size_t size;      //有效数据个数size_t capacity;  //容量大小
}SeqList;

4. 功能的具体实现

4.1 初始化顺序表

(1) 代码实现

初始化顺序表时我们可以先开辟四个空间,之后不够再进行扩容。

void SeqListInit(SeqList* p)
{assert(p);  //断言p->a =(SLDataType*)malloc(N*sizeof(SLDataType));  //初始顺序表为四个空间if (p == NULL){perror("malloc fail:");return ;}p->size = 0;  //初始数据个数为0p->capacity = 4;  //初始空间容量为4
}
(2) 复杂度分析
  • 时间复杂度:由于没有其他未知数的引入,所以所需时间是个常数,也就是O(1)。
  • 空间复杂度:动态开辟N个空间,所以空间复杂度为O(N)。

4.2 打印数据

(1) 代码实现

打印数据只用循环打印就可以了。

void SeqListPrint(const SeqList* p)
{assert(p);  //断言if (p->size == 0)  //判断顺序表是否为空{printf("顺序表为空\n");return;}int i = 0;for (i = 0; i < p->size; i++)  //打印顺序表{printf("%d ", p->a[i]);}printf("\n");
}
(2) 复杂度分析
  • 时间复杂度:因为会打印顺序表中的所有数据,所以时间复杂度为O(N)。
  • 空间复杂度:打印顺序表并不需要开辟格外的空间,所以空间复杂度为O(N)。

4.3 尾插数据

尾插数据,顾名思义就是在顺序表末尾插入数据,在插入数据之前我们应该检查顺序表是否为满。

(1) 检查是否已满
void CheckCapacity(SeqList* p)
{assert(p);  //断言if (p->size == p->capacity)  //检查容量,满了则增容{size_t newcapacity = 2 * p->capacity;  //,扩容为原来的2倍SLDataType* prt = (SLDataType*)realloc(p->a, newcapacity * sizeof(SLDataType));  //扩容if (prt == NULL){perror("realloc fail:");return ;}p->a = prt;  // prt不为空,开辟成功p->capacity = newcapacity;  //更新容量}
}
(2) 插入数据
void SeqListPushBack(SeqList* p, SLDataType x)
{assert(p);  //断言CheckCapacity(p);  //检查顺序表容量是否已满p->a[p->size] = x;  //尾插数据p->size++;  //有效数据个数+1
}
(3) 复杂度分析
  • 时间复杂度:没有变量影响时间,时间复杂度为O(1)。
  • 空间复杂度:以最坏的情况考虑,会进行扩容,空间复杂度为O(N)。

4.4 尾删数据

同理,尾删数据就是删除顺序表中最后一个元素,我们只需将size–。注意:删除之前要保证顺序表中有数据

(1) 代码实现
void SeqListPopBack(SeqList* p)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空p->size--;  //有效数据个数-1
}
(2) 复杂度分析
  • 时间复杂度:没有变量影响时间,时间复杂度为O(1)。
  • 空间复杂度:没有变量影响空间,空间复杂度为O(N)。

4.5 头插数据

头部插入数据就是在第一个元素位置插入一个元素。

(1) 代码实现
void SeqListPushFront(SeqList* p, SLDataType x)
{assert(p);  //断言CheckCapacity(p);  //检查顺序表容量是否已满int i = 0;for (i = p->size - 1; i >= 0; i--)  //顺序表中[0,size-1]的元素依次向后挪动一位{p->a[i + 1] = p->a[i];}p->a[0] = x;  //头插数据p->size++;  //有效数据个数+1
}
(2) 复杂度分析
  • 时间复杂度:因为从头部插入数据,后续数据需要依次覆盖,所以时间复杂度为O(N)。
  • 空间复杂度:因为可能会进行扩容,按最坏的情况来算,空间复杂度为O(N)。

4.5 头删数据

从头部删除数据,与尾部删除数据不同,需要依次往前覆盖。

(1) 代码实现
void SeqListPopFront(SeqList* p)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空int i = 0;for (i = 1; i < p->size; i++)  //顺序表中[1,size-1]的元素依次向前挪动一位{p->a[i - 1] = p->a[i];}p->size--;  //有效数据个数-1
}
(2) 复杂度分析
  • 时间复杂度:因为需要往前覆盖数据,所以时间复杂度自然为O(N)。
  • 空间复杂度:因为并没有开辟其他空间,所以空间复杂度为O(1)。

4.6 查找指定值

根据输入参数,在顺序表中查找指定的值并返回其下标,若未找到则返回-1。

(1) 代码实现
int SeqListFind(const SeqList* p, SLDataType x)
{assert(p);  //断言int i = 0;for (i = 0; i < p->size; i++){if (p->a[i] == x){return i;  //查找到,返回该值在数组中的下标}}return -1;  //没有查找到
}
(2) 复杂度分析
  • 时间复杂度:以最坏的情况分析,时间复杂度为O(N)。
  • 空间复杂度:并没有格外的空间消耗,空间复杂度为O(1)。

4.7 指定位置修改

我们可以通过指定下标或者查找指定值的下标来修改任意位置的值。

(1) 代码实现
void SeqListModify(SeqList* p, int pos, SLDataType x)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空assert(pos >= 0 && pos < p->size);  //检查pos下标的合法性p->a[pos] = x;  //修改pos下标处对应的数据
}
(2) 复杂度分析
  • 时间复杂度:因为是通过指定下标修改,所以时间复杂度为O(1)。
  • 空间复杂度:没有开辟额外的空间,所以空间复杂度为O(1)。

4.8 指定位置插入

和前面其他插入一样,指定位置插入也需要判断是否扩容。

(1) 代码实现
void SeqListInsert(SeqList* p, int pos, SLDataType x)
{assert(p);//断言assert(pos <= p->size); //检查pos下标的合法性SeqListCheck(p);//扩容int cur = p->size;while (cur > pos){p->a[cur] = p->a[cur - 1];//覆盖--cur;}p->a[pos] = x;p->size++;
}
(2) 复杂度分析
  • 时间复杂度:需要依次覆盖,时间复杂度为O(N)。
  • 空间复杂度:可能需要扩容,空间复杂度为O(N)。

4.9 指定位置删除

和前面的删除差不多,但是指定删除可能会依次覆盖。

(1) 代码实现
void SeqListErase(SeqList* p, int pos)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空assert(pos >= 0 && pos < p->size);  //检查pos下标的合法性int i = 0;for (i = pos + 1; i < p->size; i++)  //将pos位置后面的数据依次向前挪动一位{p->a[i - 1] = p->a[i];}p->size--;  //有效数据个数-1
}
(2) 复杂度分析
  • 时间复杂度:需要依次覆盖,时间复杂度为O(N)。
  • 空间复杂度:没有额外的空间消耗,空间复杂度为O(1)。

4.10 回收空间

在结束操作之后,需要释放开辟的空间,以防止内存泄漏。

(1) 代码实现
void SeqListDestory(SeqList* p)
{assert(p);  //断言free(p->a);   //释放动态开辟的空间p->a = NULL;  //置空p->size = 0;  //数据个数置0p->capacity = 0;  //空间容量大小置0
}
(2) 复杂度分析
  • 时间复杂度:没有额外的时间消耗,时间复杂度为O(1)。
  • 空间复杂度:没有额外的空间消耗,空间复杂度为O(1)。

5. 完整代码

5.1 SeqList.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define N 4 //初始化空间大小
typedef int SLDataType; //类型重命名,方便后续修改类型
typedef struct SeqList
{SLDataType* a;    //指向动态开辟的数组size_t size;      //有效数据个数size_t capacity;  //容量大小
}SeqList;void SeqListInit(SeqList* p);//初始化顺序表
void SeqListPrint(const SeqList* p);//打印顺序表
void SeqListPushBack(SeqList* p, SLDataType x);//尾插
void SeqListPopBack(SeqList* p);//尾删
void SeqListPushFront(SeqList* p, SLDataType x);//头插
void SeqListPopFront(SeqList* p);//头删
int SeqListFind(const SeqList* p, SLDataType x);//查找数据
void SeqListModify(SeqList* p, int pos, SLDataType x);//修改数据
void SeqListInsert(SeqList* p, int pos, SLDataType x);//指定插入
void SeqListErase(SeqList* p, int pos);//指定删除
void SeqListDestory(SeqList* p);//释放空间

5.2 SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void SeqListInit(SeqList* p)
{assert(p);  //断言p->a =(SLDataType*)malloc(N*sizeof(SLDataType));  //初始顺序表为四个空间if (p == NULL){perror("malloc fail:");return ;}p->size = 0;  //初始数据个数为0p->capacity = 4;  //初始空间容量为4
}
void CheckCapacity(SeqList* p)
{assert(p);  //断言if (p->size == p->capacity)  //检查容量,满了则增容{size_t newcapacity = 2 * p->capacity;  //,扩容为原来的2倍SLDataType* prt = (SLDataType*)realloc(p->a, newcapacity * sizeof(SLDataType));  //扩容if (prt == NULL){perror("realloc");return ;}p->a = prt;  // prt不为空,开辟成功p->capacity = newcapacity;  //更新容量}
}
void SeqListPushBack(SeqList* p, SLDataType x)
{assert(p);  //断言CheckCapacity(p);  //检查顺序表容量是否已满p->a[p->size] = x;  //尾插数据p->size++;  //有效数据个数+1
}
void SeqListPrint(const SeqList* p)
{assert(p);  //断言if (p->size == 0)  //判断顺序表是否为空{printf("顺序表为空\n");return;}int i = 0;for (i = 0; i < p->size; i++)  //打印顺序表{printf("%d ", p->a[i]);}printf("\n");
}
void SeqListPopBack(SeqList* p)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空p->size--;  //有效数据个数-1
}
void SeqListPushFront(SeqList* p, SLDataType x)
{assert(p);  //断言CheckCapacity(p);  //检查顺序表容量是否已满int i = 0;for (i = p->size - 1; i >= 0; i--)  //顺序表中[0,size-1]的元素依次向后挪动一位{p->a[i + 1] = p->a[i];}p->a[0] = x;  //头插数据p->size++;  //有效数据个数+1
}
void SeqListPopFront(SeqList* p)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空int i = 0;for (i = 1; i < p->size; i++)  //顺序表中[1,size-1]的元素依次向前挪动一位{p->a[i - 1] = p->a[i];}p->size--;  //有效数据个数-1
}
int SeqListFind(const SeqList* p, SLDataType x)
{assert(p);  //断言int i = 0;for (i = 0; i < p->size; i++){if (p->a[i] == x){return i;  //查找到,返回该值在数组中的下标}}return -1;  //没有查找到
}
void SeqListModify(SeqList* p, int pos, SLDataType x)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空assert(pos >= 0 && pos < p->size);  //检查pos下标的合法性p->a[pos] = x;  //修改pos下标处对应的数据
}
void SeqListInsert(SeqList* p, int pos, SLDataType x)
{assert(p);//断言assert(pos <= p->size); //检查pos下标的合法性SeqListCheck(p);//扩容int cur = p->size;while (cur > pos){p->a[cur] = p->a[cur - 1];//覆盖--cur;}p->a[pos] = x;p->size++;
}
void SeqListErase(SeqList* p, int pos)
{assert(p);  //断言assert(p->size > 0);  //顺序表不能为空assert(pos >= 0 && pos < p->size);  //检查pos下标的合法性int i = 0;for (i = pos + 1; i < p->size; i++)  //将pos位置后面的数据依次向前挪动一位{p->a[i - 1] = p->a[i];}p->size--;  //有效数据个数-1
}
void SeqListDestory(SeqList* p)
{assert(p);  //断言free(p->a);   //释放动态开辟的空间p->a = NULL;  //置空p->size = 0;  //数据个数置0p->capacity = 0;  //空间容量大小置0
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mitmproxy代理

文章目录 mitmproxy1. 网络代理2. 安装3. Https请求3.1 启动mitmproxy3.2 获取证书3.3 配置代理3.4 运行测试 4. 请求4.1 读取请求4.2 修改请求4.3 拦截请求 5. 响应5.1 读取响应5.2 修改响应 6. 案例&#xff1a;共享账号6.1 登录bilibili获取cookies6.2 在代理请求中设置cook…

性能优化篇(二) 静态合批步骤与所有注意事项\游戏运行时使用代码启动静态合批

静态合批步骤: 1.开启Project Settings —>Player–>Other Setting里勾选Static Batching选项(一般情况下unity都是默认勾选状态) 2.勾选需要合批的静态物体上的Batching Static项,勾选后此物体下的所有子物体都默认参与静态合批(勾选后物体不能进行移动/旋转/缩放操作,…

逻辑回归与决策边界解析

目录 前言1 逻辑回归基础1.1 Sigmoid函数&#xff1a;打开分类之门1.2 决策函数&#xff1a;划定分类界限1.3 逻辑回归详解 2 决策边界2.1 线性决策边界2.2 非线性决策边界2.3 决策边界的优化 3 应用与实例3.1 垃圾邮件分类&#xff1a;精准过滤3.2 金融欺诈检测&#xff1a;保…

揭示IP风险画像的作用与价值

在当今数字化时代&#xff0c;互联网的快速发展为企业和个人带来了巨大的机遇&#xff0c;同时也带来了各种安全风险和威胁。随着网络攻击手段的不断升级和演变&#xff0c;传统的安全防御手段已经无法满足对抗复杂多变的网络威胁的需求。IP风险画像作为一种新型的网络安全解决…

MYSQL的优化学习,从原理到索引,在到事务和锁机制,最后的主从复制、读写分离和分库分表

mysql的优化学习 为什么选择Mysql不选择其他的数据库&#xff1f;还有哪些&#xff0c;有什么区别&#xff1f; Mysql&#xff1a;开源免费版本可用&#xff0c;适用于中小型应用 Oracle&#xff1a;适用于大型企业级应用&#xff0c;复杂的业务场景和大量数据的处理&#xf…

基础小白快速入门Python------>模块的作用和意义

模块&#xff0c; 这个词听起来是如此的高大威猛&#xff0c;以至于萌新小白见了瑟瑟发抖&#xff0c;本草履虫见了都直摇头&#xff0c;好像听上去很难的样子&#xff0c;但是但是&#xff0c;年轻人&#xff0c;请听本少年细细讲述&#xff0c;他只是看起来很难&#xff0c;实…

七、基于FreeRTOSSTM32移植MQTT

1、移植环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 (4)已移植好FreeRTOS和调试好串口的项目。 FreeRTOS移植参考博客&#xff1a;示例1&#xff1a;FreeRTOS移植详解_基于HAL库工程_hal库移植rtos-CSDN博客mqttclient源码&#xff1a;htt…

GIS之深度学习01:检测电脑是否包含英伟达GPU

GPU&#xff08;Graphics processing unit&#xff09;&#xff0c;中文全称图形处理器&#xff0c;我们听说的更多的CPU全称是central processing unit&#xff0c;中央处理器。研究深度学习和神经网络大都离不开GPU&#xff0c;在GPU的加持下&#xff0c;我们可以更快的获得模…

Linux搭建SFTP服务器

案例&#xff1a;搭建SFTP服务器 SFTP&#xff08;SSH文件传输协议&#xff09; SFTP&#xff08;SSH文件传输协议&#xff09;是一种安全的文件传输协议&#xff0c;用于在计算机之间传输文件。它基于SSH&#xff08;安全外壳协议&#xff09;的子系统&#xff0c;提供了加密的…

java 基础(核心知识搭配代码)

前言 java的学习分为了上部分以及下部分进行学习&#xff0c;上部分就是对于java的基础知识&#xff0c;面向对象上&#xff0c;面向对象下&#xff0c;异常操作&#xff0c;javaApi&#xff1b;下部主要是集合&#xff0c;泛型&#xff0c;反射&#xff0c;IO流&#xff0c;J…

加密隧道技术

在现在的互联网上传输数据&#xff0c;首要考虑的就是安全。这关乎到你的隐私&#xff0c;个人信息&#xff0c;财产安全等等重大问题。如果你的程序本身传输的信息没有加密&#xff0c;也可以通过其他辅助方式让你的通信加密。一些工具的就是为了解决这样的场景的&#xff0c;…

ubuntu20下使用 torchviz可视化计算图

安装 torchviz&#xff1a; pip install torchviz示例代码&#xff1a;下面是一个简单的示例代码&#xff0c;展示如何使用 torchviz 可视化计算图&#xff1a; python import torch from torchviz import make_dot# 创建一个简单的模型 model torch.nn.Sequential(torch.nn…