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

文章目录

  • 前言
  • 一、什么是顺序表
  • 二、实现顺序表
  • 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.mzph.cn/news/654194.shtml

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

相关文章

LeetCode(2)

目录 概念解释 栈 队列 树 树的概念 结点的分类 有序树 无序树 森林 二叉树 满二叉树 完全二叉树 二叉排序树 平衡二叉树 1.用栈实现队列 解法&#xff1a;双栈 2.字符串解码 解法&#xff1a;栈 3.二叉树的中序遍历 解法一&#xff1a;递归 解法二&#xff…

云微呼探索人工智能机器人对话:过去、现在和未来

随着科技的迅速发展&#xff0c;人工智能&#xff08;AI&#xff09;机器人已经成为我们日常生活中的重要一部分。从简单的语音助手到能够进行复杂对话的智能机器人&#xff0c;AI技术正在改变着我们与机器之间的互动方式。本文将探讨人工智能机器人对话的历史、现状以及未来发…

[SwiftUI]系统弹窗和自定义弹窗

一、系统弹窗 在 SwiftUI 中&#xff0c;.alert 是一个修饰符&#xff0c;用于在某些条件下显示一个警告对话框。Alert 可以配置标题、消息和一系列的按钮。每个按钮可以是默认样式、取消样式&#xff0c;或者是破坏性的样式&#xff0c;它们分别对应不同的用户操作。 1.Aler…

前端qrcode生成二维码详解

文章目录 前言1、浏览器支持2、优点3、缺点4、相关方法5、安装及使用示例 前言 qrcode 是一个基于JavaScript的二维码生成库&#xff0c;主要是通过获取 DOM 的标签&#xff0c;再通过 HTML5 Canvas 绘制而成&#xff0c;不依赖任何库。 官方文档&#xff1a;https://www.npm…

Kafka-服务端-GroupCoordinator

在每一个Broker上都会实例化一个GroupCoordinator对象&#xff0c;Kafka按照Consumer Group的名称将其分配给对应的GroupCoordinator进行管理&#xff1b; 每个GroupCoordinator只负责管理Consumer Group的一个子集&#xff0c;而非集群中全部的Consumer Group。 请注意与Kaf…

Java项目:基于SSM框架实现的企业员工岗前培训管理系统(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm821基于ssm框架实现的企业员工岗前培训管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格…

NetExec:一款功能强大的自动化网络安全评估与漏洞测试工具

关于NetExec NetExec是一款功能强大的自动化网络安全评估与漏洞测试工具&#xff0c;该工具可以帮助广大研究人员以自动化的形式测试大型网络的安全&#xff0c;并通过利用网络服务漏洞来评估目标网络的安全态势。 支持的协议 1、SMB协议 2、LDAP协议 3、WinRM协议 4、MSSQL协…

go语言函数进阶

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

vue-head 插件设置浏览器顶部 favicon 图标 - 动态管理 html 文档头部标签内容

目录 需求实现11. 安装插件2. 项目内 main.js 引入3. vue页面使用 实现2其他 需求 vue项目中浏览器页面顶部图标可配置 实现1 使用 vue-head 插件实现 vue-head 插件可实现 html 文档中 head 标签中的内容动态配置&#xff08;npm 官网 vue-head 插件&#xff09; 1. 安装插件 …

promethues基础概念

promethues是一个开源的系统监控以及报警系统&#xff0c;整个zabbix的功能&#xff0c;系统&#xff0c;网络&#xff0c;设备 promethues可以兼容网络和设置被&#xff0c;容器监控&#xff0c;告警系统&#xff0c;因为他和k8s是一个项目基金开发的产品&#xff0c;天生匹配…

代码随想录算法训练营第34天 | 1005.K次取反后最大化的数组和 134.加油站 135.分发糖果

K次取反后最大化的数组和 贪心局部最优&#xff1a;将绝对值大的负数变为正数&#xff0c;当前和变为最大&#xff1b;全局最优&#xff1a;整体获得最大和。 如果负数都变成正数之后&#xff0c;k > 0&#xff0c;仍然需要继续翻转&#xff0c;贪心局部最优&#xff1a;将最…

14.STM32F4 LCD屏幕概念及源码下载(LCD之一)

一、LCD液晶显示屏介绍 1、常见的显示设备 在目前市面上&#xff0c;常见的显示设备种类有&#xff1a;LED、显示数码管、点阵LED显示屏、LCD液晶显示屏&#xff0c;这几种设备的特点是&#xff1a; &#xff08;1&#xff09;LED LED灯是最简单的显示设备&#xff0c;它只有两…

天拓四方:物联网网关在机械制造企业的应用

随着物联网技术的不断发展&#xff0c;越来越多的机械制造企业开始探索如何利用物联网技术提升生产效率、降低运营成本。物联网网关作为物联网架构中的关键设备&#xff0c;能够实现设备间的数据交互与远程控制&#xff0c;为机械制造企业带来了巨大的商业价值。它能够实现设备…

漏洞原理XSS存贮型漏洞

漏洞原理XSS存贮型漏洞 XSS&#xff08;跨站脚本攻击&#xff09;是一种常见的Web安全漏洞&#xff0c;它允许攻击者将恶意代码注入到网页中&#xff0c;进而攻击用户的浏览器。存储型XSS漏洞是一种特定类型的XSS漏洞&#xff0c;它发生在Web应用程序中&#xff0c;其中用户输入…

小红叒战小紫

概率dp #include <iostream> #include <string> #include <stack> #include <vector> #include <queue> #include <deque> #include <set> #include <map> #include <unordered_map> #include <unordered_set> #…

在租户内启用SharePoint Embedded

要开启 SharePoint Embedded&#xff0c;你得是管理员&#xff0c;然后按照这些步骤操作&#xff1a; 登录到你的 SharePoint 管理中心。在左边的菜单里找到“设置”选项&#xff0c;点进去。 3. 在设置页面里找到“SharePoint Embedded 应用”。 4.如果这个功能还没开启&…

Android Settings 显示电池点亮百分比

如题&#xff0c;Android 原生 Settings 里有个 电池电量百分比 的选项&#xff0c;打开后电池电量百分比会显示在状态栏。 基于 Android 13 &#xff0c; 代码在 ./packages/apps/Settings/src/com/android/settings/display/BatteryPercentagePreferenceController.java &am…

【flutter项目类型】project type如何区分

通过项目中.metadata内容区分 如 # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited.version:revision: 85684f9300908116a78138ea4c6036c35c9a1236channel: stablep…

【大数据】Flink 架构(二):数据传输

《Flink 架构》系列&#xff08;已完结&#xff09;&#xff0c;共包含以下 6 篇文章&#xff1a; Flink 架构&#xff08;一&#xff09;&#xff1a;系统架构Flink 架构&#xff08;二&#xff09;&#xff1a;数据传输Flink 架构&#xff08;三&#xff09;&#xff1a;事件…

(Aliexpress)速卖通卖家通过自养号补单提高出单率

在跨境电商领域&#xff0c;有些卖家可能会遇到这样的问题&#xff1a;自己的速卖通店铺始终没有订单产生。那么&#xff0c;当速卖通店铺一直不出单时&#xff0c;我们应该如何进行补救呢&#xff1f;今天珑哥将围绕这个问题展开探讨&#xff0c;并分享一些提升速卖通店铺销量…