锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. 数据结构之顺序表

数据结构之顺序表

0
  • 软件开发
  • 发布于 2024-09-21
  • 0 次阅读
黄健
黄健

目录

1.线性表

2.顺序表

2.1概念及结构

1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

2.2接口实现

销毁顺序表

打印顺序表

增加数据

头插

尾插

指定下标位置插入

删除数据

头删

尾删

指定下标位置删除

查找数据

修改数据


1. 线性表

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

2.顺序表

2.1概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:

1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

实用动态内存开辟函数去开辟空间的时候,每次空间不够的时候都会扩容,每次扩容都会付出一定程度上的代价。所以每次扩容可以稍微多扩容一点点,减少扩容的频率。扩容是根据需求扩容,那我们最常用的扩容倍数就是:2倍 1.5倍。但是不是一定要求是这些倍数。2倍数是一个相对合适的量,扩容的频率会越来越低,具体情况具体分析!

2.2接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空
间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间
大小,所以下面我们实现动态顺序表。
第一步:初始化顺序表

typedef int SLDataType;//本篇博客以存放整型数据为例

typedef struct SeqList
{
    SLDataType* a;//声明了一个指向顺序表的指针,姑且称它为"顺序表指针"
    int size;//记录当前顺序表内元素个数
    int capacity;//记录当前顺序表的最大容量
}SeqList;

接着开始初始化

//初始化顺序表
void SeqListInit(SeqList* ps)
{
    assert(ps);
    ps->a = NULL;//刚开始时顺序表为空,顺序表指针为NULL
    ps->size = 0;//起始时元素个数为0
    ps->capacity = 0;//容量为0
}

销毁顺序表

因为顺序表所用的内存空间是动态开辟在堆区的,所以我们在使用完后需要及时对其进行释放,避免造成内存泄漏。

//销毁顺序表
void SeqListDestory(SeqList* ps)
{
    assert(ps);
    free(ps->a);//释放顺序表指针指向的空间
    ps->a = NULL;//及时置空
    ps->size = 0;//元素个数置0
    ps->capacity = 0;//容量置0
}

打印顺序表

循环打印size个元素即可

//打印顺序表
void SeqListPrint(SeqList* ps)
{
    assert(ps);
    int i = 0;
    //循环打印顺序表指针指向的数据
    for (i = 0; i < ps->size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}

增加数据

仔细想想,我们每次需要增加数据的时候,首先都应该先检查顺序表内元素个数是否已达顺序表容量上限。若已达上限,那么我们就需要先对顺序表进行扩容,然后才能增加数据。

//检查顺序表容量是否已满,若已满,则增容
void SeqCheckCapacity(SeqList* ps)
{
    if (ps->size == ps->capacity)//满了,需要增容
    {
        //判断顺序表容量是否为0,若为0,则先开辟用于存放4个元素的空间大小,若不为0,则扩容为原来容量的两倍
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLDataType* newA = realloc(ps->a, newcapacity*sizeof(SLDataType));
        if (newA == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->a = newA;//开辟成功,将顺序表指针更新
        ps->capacity = newcapacity;//容量更新
    }
}

若传入realloc的指针为空指针(NULL),则realloc函数的作用相当于malloc函数,这样也不会对新开辟的空间进行初始化

头插

要想在顺序表的表头插入数据,那么就需要先将顺序表原有的数据从后往前依次向后挪动一位,最后再将数据插入表头。

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
    assert(ps);
    SeqCheckCapacity(ps);//检查容量
    int i = 0;
    for (i = ps->size; i > 0; i--)//将数据从后往前依次向后挪
    {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[0] = x;
    ps->size++;//顺序表元素个数加一
}

挪动数据的时候应从后向前依次挪动,若从前向后挪动,会导致后一个数据被覆盖。

尾插

尾插相对于头插就比较简单了,直接在表尾插入数据即可

//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
    assert(ps);
    SeqCheckCapacity(ps);//检查容量
    ps->a[ps->size] = x;
    ps->size++;//顺序表元素个数加一
}

指定下标位置插入

要做到在指定下标位置插入数据,首先我们需要得到一个下标位置,然后从该下标位置开始(包括该位置),其后的数据从后往前依次向后挪动一位,最后将数据插入到该下标位置。

//指定下标位置插入
void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->size);//检查输入下标的合法性
    SeqCheckCapacity(ps);//检查容量
    int i = 0;
    for (i = ps->size; i > pos; i--)//从pos下标位置开始,其后的数据从后往前依次向后挪
    {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[pos] = x;
    ps->size++;//顺序表元素个数加一
}

观察到,头插和尾插实际就是再下标为0和下标为size的地方进行插入,意味着我们可以统一使用该函数来实现

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
    SeqListInsert(ps, 0, x);//在下标为0的位置插入数据
}
//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
    SeqListInsert(ps, ps->size, x);//在下标为ps->size的位置插入数据
}

删除数据

删除数据,其实可以理解为:从某个位置开始,数据依次向前覆盖。这样一来,该位置的数据就相当于删除了

头删

要删除表头的数据,我们可以从下标为1的位置开始,依次将数据向前覆盖即可。

//头删
void SeqListPopFront(SeqList* ps)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    int i = 0;
    for (i = 0; i < ps->size - 1; i++)//将数据从前往后依次向前覆盖
    {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;//顺序表元素个数减一
}

注意:数据覆盖的时候应从前向后依次覆盖,若从后向前覆盖,会导致前一个数据被覆盖。

尾删

尾删就更简单了,直接将顺序表的元素个数减一即可

//尾删
void SeqListPopBack(SeqList* ps)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    ps->size--;//顺序表元素个数减一
}

指定下标位置删除

要删除指定下标位置的数据,我们只需要从下标位置开始,其后的数据从前向后依次覆盖即可。

//指定下标位置删除
void SeqListErase(SeqList* ps, int pos)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    assert(pos >= 0 && pos < ps->size);
    int i = 0;
    for (i = pos; i < ps->size - 1; i++)//从pos下标位置开始,其后的数据从前往后依次向前覆盖
    {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;//顺序表元素个数减一
}

同样的道理,头删和尾删实际上也就是删除下标为0的位置和下标为ps->size - 1的位置的数据,也就意味着我们可以统一使用该函数来实现头删和尾删。

//头删
void SeqListPopFront(SeqList* ps)
{
    SeqListErase(ps, 0);//删除下标为0的位置的数据
}
//尾删
void SeqListPopBack(SeqList* ps)
{
    SeqListErase(ps, ps->size - 1);//删除下标为ps->size - 1的位置的数据
}

查找数据

查找数据也相对简单,直接遍历一次顺序表即可,若找到了目标数据,则停止遍历,并返回该数据的下标,否则返回-1。

//查找元素,若有,返回下标,否则返回-1
int SeqListFind(SeqList* ps, SLDataType x)
{
    assert(ps);
    int i = 0;
    for (i = 0; i < ps->size; i++)//遍历顺序表进行查找
    {
        if (ps->a[i] == x)
            return i;//找到该数据,返回下标
    }
    return -1;//未找到,返回-1
}

修改数据

修改数据,就直接对该位置的数据进行再次赋值即可。

//修改指定下标位置元素
void SeqListModify(SeqList* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos < ps->size);//检查输入下标的合法性
    ps->a[pos] = x;//修改数据
}

原文链接: https://blog.csdn.net/2302_80084329/article/details/135049187

标签: #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.