数据结构之手撕顺序表(讲解➕源代码)

简介: 数据结构之手撕顺序表(讲解➕源代码)

0.引言

       在本章之后,就要求大家对于指针、结构体、动态开辟等相关的知识要熟练的掌握,如果有小伙伴对上面相关的知识还不是很清晰,要先弄明白再过来接着学习哦!

       那进入正题,在讲解顺序表之前,我们先来介绍线性表这个数据结构。


0.1 线性表


    线性表是 n个具有相同特性的数据元素组成的有限的序列。

       并且在逻辑上是一对一的,一个接着一个的。比如我们之前学过的数组,字符串等。


       相同特性:同一种数据类型

       有限:数据元素的个数是有限的


       常见的线性表:顺序表、链表、栈、队列、字符串等。


       我们在讲解数据结构的时候通常要先看它的逻辑结构和物理结构。

0.2 线性表的逻辑结构和物理结构

0.2.1 逻辑结构


线性表的逻辑结构是线性结构,线性结构 是一条连续的直线,也就是说 线性表在逻辑上是连续的,比如我们在C语言学过的的数组(顺序表),指针(可以构成链表)。

上图分别为顺序表链表,他们在逻辑结构上都是一个接着一个,连续的。但是在物理结构他们还依旧连续吗?让我们带着疑问往下走。

0.2.2 线性表的物理结构

明确告诉大家,线性表在物理结构上不一定连续,因为我们可以构成线性表的结构有数组和指针,指针又被称作链式结构。


那什么时候是连续的?

当线性表是由数组构成时:物理结构连续


       线性表的物理结构一定连续,因为数组是一个一个挨着的空间,地址上是紧挨着的,所以是连续的。

如图:


什么时候又不是连续的呢?


当线性表为链式结构时:物理结构不连续


首先链式结构在逻辑上一定是连续的,因为我们可以通过指针就找到该指针对应的地址。

       但指针的地址不一定是连续的,我们可以这存一个,那存一个,通过指针给他们链接起来。

如图:



当了解了线性表的物理结构和逻辑结构之后,就让我们一起学习第一种数据结构---顺序表吧!

1. 顺序表

1.1概念


顺序表是 用一段物理地址连续的存储单元依次存储数据元素的线性结构,通常采用数组的形式存储。在数组上完成数据的增删查改。

       这是什么意思呢?首先顺序表是物理地址连续的,那物理地址连续,就应该是用数组的形式来存储,之后根据数组的性质进行数据的增加,删除,查找和更改。


       我们知道顺序表是由什么构成之后,让我们看看顺序表都分为哪几种吧!

1.2 顺序表的分类

       我们顺序表只分为静态顺序表和动态顺序表两种,下面我们会给大家展示这两种顺序表。

1.2.1 静态顺序表

       静态顺序表指的是利用定长数组来存储元素

//顺序表的静态存储
#define N 7 //顺序表一次开辟的空间个数
typedef int SLDataType; //将数据类型重命名,以便我们未来换用其他的数据类型
typedef struct SeqList
{
    SLDataType arr[N]; //定长数组
    size_t size; //有效的数据个数,size_t指的是无符号整型
}Seqlist;


91f0806a50c74601877ee0d0bdc002a9.png


     我们在使用静态顺序表的时候,只能每次开辟N个大小的空间,这也就要求我们在使用之前就要想好你要存放多少个数据,非常不灵活,没有办法做到你想插入几个数据的时候就插入几个数据,所以我们大多时候不使用静态顺序表,而是改用动态顺序表作为我们日常应用。


这也是我们常说的要适应大环境的需要,而不是一味不变。

1.2.2 动态顺序表

       动态顺序表:使用动态开辟的数组存储。在这里需要大家对动态开辟内存知识有一定的掌握。

1. 动态顺序表的定义

我们首先要明确自己需要什么


1.动态开辟的数组

2.有效的数据个数(你存入的数据个数)

3.数组的容量(开辟的空间大小)


这三个数据是绑定一起的吧!因为你有数组,你才可以存入元素,你存入元素,你的有效的数据个数才会增加,而在你存入元素之前,你必须开辟空间,给数组一定的容量。


所以我们在这里用了结构体包含三者,目的就是能让他们三个绑定,便于大家完成代码。

       未来大家如果要创造更多的关系数据(具有关系的数据),强烈推荐使用结构体来给它们打包。

typedef int SLDataType; //数据类型的重命名,方便更改数据类型
typedef struct SeqList
{
    SLDataType *a; //指向动态开辟的数组
    int size;     //有效的数据个数
    int capacity; //动态开辟的数组的容量
}SL;
2.初始化

 在初始化这里,我们要给数组开辟一定的空间,方便在插入数据的时候进行操作,但是在这里,我们只是开辟空间,并没有给数组插入元素,所以我们的有效元素个数size就是0,容量就是你开辟的空间个数。

我们一般开辟空间第一次给4个数据类型的空间。但是这个没有定性要求(固定的要求)你想开辟几个就开辟几个。

void SLInit(SL*ps) //初始化
{
    ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4);
    if(ps->a == NULL)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    ps ->size = 0;
    ps ->capacity = 4;
}


3.退出程序时的销毁


注意⚠️⚠️⚠️⚠️⚠️⚠️


       这个函数有讲头了,我们要记住一点,凡是通过动态开辟的空间,一定要进行销毁释放,因为由malloc,realloc等开辟的空间是在堆上开辟的,无法自动释放掉。我们没有这个函数,那么就会造成内存的泄漏。


       那么如何释放呢?

  free函数来释放开辟的空间,谁被开辟free谁,之后要将free的对象置为空就OK啦!不要忘了你释放空间之后,有效的数据元素是0了哈,容量也是0.

void SLDestroy(SL*ps) //退出时销毁
{
    free(ps->a);
    ps->a = NULL;
    ps->size = 0;
    ps->capacity = 0;
}
4.扩容


 这个函数是解决当你第一次开辟的空间不够的问题,就要用到这个函数来进行扩容,扩容一般是扩原来空间的二倍这么大。

       那扩容的条件是什么呢?

 就是当我们插入的  有效元素的个数size = 容量capacity  的时候,完成扩容之后一定要判断你扩容是否成功了,如果 tmp🟰NULL,那就说明开辟空间失败,用perror函数告诉你哪里失败了,之后用exit函数退出程序,exit函数是直接强制退出,不回到主函数,跟return不一样。当开辟成功了,就让a指向这段空间就OK,再更新一下capacity;

void SLCheckCapacity(SL*ps)  //扩容函数
{
    if(ps->size == ps->capacity)
    {
        SLDataType *tmp = (SLDataType*)realloc(ps->a,((sizeof(SLDataType)) * ((ps->capacity) * 2)));
        if(tmp == NULL)
        {
            perror("realloc");
            exit(EXIT_FAILURE);
        }
        ps -> a = tmp;
        ps->capacity *= 2;
    }
}
6.尾插尾删
      顺序表的尾插:

       在尾插的时候,我们要判断是否插满了,就需要用到我们的扩容函数来判断一下,没满就直接插入,满了则扩容。


如图:这就是没有插满的情况,我们目前已经插入了5个元素,ps->size=5,我们再插入一个元素的时候就可以在下标为size这里插入了,之后再size++就可以了


如图:是插满的情况,我们就要先扩容



如图:扩容之后,这个时候我们就可以插入啦!


顺序表的尾删:

       尾删就好写多了,我们只需要将size减1即可,因为size代表有效的元素个数,将元素个数减一,就相当于删除了。

尾插
void SLPushBack(SL*ps,int i) 
{
    SLCheckCapacity(ps);
    ps->a[ps->size] = i;
    ps->size++;
}
尾删
void SLPopBack(SL*ps) 
{
    assert(ps->size > 0);
    ps->size--;
}
5.头插头删
顺序表的头插:


      对于头插就要麻烦很多了,我们需要将数据一个个覆盖给下一个。再将我们的第一个元素值变成要插入的元素值,这里也要判断是否需要扩容哦!



 顺序表的头删:

       同理头删也是需要覆盖数据的,要把第二个数据给第一个,第三个给第二个等等以此类推

头插
void SLPushFront(SL*ps,int i)
{
    SLCheckCapacity(ps);
    int end = ps->size;
    for(;end - 1 >= 0 ; end--)
    {
        ps->a[end] = ps->a[end - 1];
    }
    ps->a[0] = i;
    ps->size++;
}
 ///头删
void SLPopFront(SL*ps)
{
    assert(ps->size > 0);
    int i = 0;
    for(i = 0 ; i + 1 < ps->size ; i++)
    {
        ps->a[i] = ps->a[i+1];
    }
    ps->size--;
}
7.顺序表的查找

       查找某一值x是否存在顺序表里,存在返回数组下标,不存在返回-1.

int SeqListFind(SL*ps,SLDataType x) //查找
{
    int i = 0;
    for(i = 0 ; i < ps->size ; i++)
    {
        if(ps->a[i] == x)
        {
            return i;
        }
    }
    return -1;
}
8.在下标为pos的位置的插入

下面的大家自行画图理解哦,相信经过前面的讲解,大家有一定的收获啦!

void SeqListInsert(SL* ps, int pos, SLDataType x)// 顺序表在pos位置插入x
{
    if(ps->size == ps->capacity)
    {
        SLCheckCapacity(ps);
    }
    int i = SeqListFind(ps,pos);
    int j = ps->size;
    for(;j > i ; j--)
    {
        ps->a[j] = ps->a[j-1];
    }
    ps->a[i] = x;
    ps->size++;
}
9.删除下标为pos位置的值
void SeqListErase(SL* ps, int pos)// 顺序表删除pos位置的值
{
    assert(ps->size > 0);
    int i = SeqListFind(ps,pos);
    for(i;i < ps->size - 1; i++ )
    {
        ps->a[i] = ps->a[i+1];
    }
    ps->size--;
}
9.打印

打印就是遍历一边数组就OK啦!

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


以上就是顺序表的相关接口实现啦!谢谢大家过来与博主一起学习,如果觉得博主写的还可以,对各位有帮助,别忘了点赞和收藏,方便以后再次查看呀!

相关文章
|
1月前
|
存储 编译器 C语言
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
52 2
|
24天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
24天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
56 3
|
24天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
1月前
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
31 6
|
1月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
25 3
|
1月前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
21 2
|
1月前
|
存储 算法 索引
【数据结构】——顺序表
【数据结构】——顺序表
|
1月前
|
存储
【数据结构】线性表和顺序表
【数据结构】线性表和顺序表
22 1
|
1月前
|
存储
数据结构1——顺序表
数据结构1——顺序表
17 1