c数据结构什么是顺序表?一文迅速搞懂!(代码片段)

JasonTuring JasonTuring     2023-01-17     271

关键词:


线性表是数据结构中最常见的 一种数据存储方式。线性表分为顺序表与链表。本文将会从简单的顺序表开始,一点一点揭开C数据结构的神秘面纱。

一、什么是线性表?

线性表,顾名思义,其会呈现出一种线性进行存储。这和我们所学过的数组的概念很像,回顾一下数组的基本知识:数组的各个元素连续存放在内存中,并按照数组类型为每一个变量分配对应大小的存储空间。在线性表中也是如此,当内存中的数据是连续存储时我们可以将其用数组的角度理解,我们称之为顺序表。当数据以指针的形式连接起来时,此时我们称其为链表。

二、如何创建一个顺序表?

由上面的叙述,我们可以知道,一个顺序表可以近似理解为一个数组。但与数组不同的是,顺序表本质上是一个需要不断的进行数据的存储、删除动态空间。所以我们需要用多个接口函数来维护顺序表。

由于顺序表不仅仅是一个数组,还需要我们不断的探索其长度,所以我们并不能简单的通过创建数组的方式创建顺序表。所以我们想到用一个结构体就能够有效的将数组与其大小紧密联合起来:

#pragma once
#define N 1000
typedef int SLDataType;

//静态数据表
typedef struct SeqList

    SLDataType a[N];
    int size;
SL;

注意到,这里的N是我们预先进行定义的1000,若我们需要更多的数据存储进顺序表,那么我们需要不断的修改#define N的值。这显得有些麻烦。
所以稍微改进一下,我们就可以用一个指针将静态顺序表转变为一个可以扩展空间的动态顺序表。

typedef struct SeqList

    SLDataType *a;
    int size;//表示数组实际存储了多少个数据
    int capacity//数据实际能存储数据的空间容量是多大
SL;

注意到,在创建动态顺序表时,我们又引入了一个capacity变量来探索当前顺序表的“存储数据的能力”而选择是否进行扩容。capacity变量在后续的接口函数中起着至关重要的作用。请注意,这里的size是当前顺序表实际存储数据的多少,capacity是整个顺序表的容量。比如说容量可以是8(capacity=8),但当前顺序表可以只存储4个元素(size=4)

三、接口函数

一个动态的顺序表需要用多个接口函数进行维护,以实现对数据元素的增删查改操作。我们定义如下的接口函数类型:

void SeqListCheckCapacity(SL*ps)
//容量判断函数
void SeqListInit(SL* ps);
//初始化函数
void SwqListDestory(SL*ps);
//销毁函数
void SeqListPushBack(SL* ps,SLDataType x);
//尾插
void SeqListPopBack(SL* ps);
//尾删
void SeqListPushFront(SL*ps,SLDataType x);
//头插
void SeqListPopFront(SL*ps);
//头删
int SeqListFind(SL* ps,SLDataType x);
//找到了返回x位置下标,没有找到返回-1
void SeqListInsert(SL*ps,int pos,SLDataType x);
//指定pos下标位置插入
void SeqListErase(SL*ps,int pos);
//删除pos位置的数据

容量审查函数:

但是在写入或者读出数据之前,我们需要先对线性表空间进行审查,以确定能否进行后续的操作。
函数创建的基本思想当前存储数据与容量相等时,说明顺序表已经没有多余空间进行增加操作,那么需要对其扩容。只有在第一次扩容时,容量为4。之后均用当前容量的二倍形式进行扩容。

void SeqListCheckCapacity(SL*ps)

    if(ps->size==ps->capacity)
    
        int newcapacity=ps->capacity==0?4:ps->capacity*2;//三目运算符
        SLDataType *tmp=(SLDataType*)realloc(ps->a,newcapcity*sizeof(SLDataType));//realloc追加内存空间进行扩容
        if(tmp==NULL)
        
            printf("realloc fail\\n");
            exit(-1);
        
        ps->a=tmp;
        ps->capacity=newcapaticy;
    

初始化函数:

//初始化函数
void SeqListInit(SL* ps)

    ps->a=NULL;
    ps->size=ps->capacity=0;
//这里要注意,要对结构体进行传址操作,否则不能进行初始化
//结构体变量本质上是一个变量,不是一个函数。通过函数指针的学习我明白了函数的名字就是函数的地址,当使用回调函数时可以&函数名 也可以不进行取地址操作。但对于一个变量来说,必须进行传址操作才能改变其值

销毁函数:

当使用完内存之后,需要对顺序表申请的内存空间进行销毁,以防止内存泄漏。

//销毁函数
void SeqlistDestory(SL*ps)

    free( ps->a);
    ps->a=NULL;
    ps->sz=ps->capacity=0;

尾插函数:

对于一个动态的顺序表来说,尾插一种重要的元素增加方式,但在尾插之前我们需要检查当前顺序表的容量是否已满。元素插入的思想便是向数组末尾进行插入数据。注意:size表示的是数组长度,而对应的最后一个元素的角标应是size-1

void SeqListPushBack(SL* ps,SLDataType x)//尾插

    //首先要判断当前空间够不够用
    //第一种情况:第一次插入之前,是空的数组
    //第一种情况:当前空间不够
    //第三种情况:当前空间足够
    //通过调用容量检查函数检查空间是否足够
    SeqListCheckCapacity(pc);
    ps->a[ps->size]=x;
    ps->size++;

尾删函数:

尾删函数的基本思想更为简单,只需要对size维护空间进行缩小,即对size以外的内容不进行访问就完成了删除操作。

void SeqListPopBack(SL* ps);//尾删

    //最基本的思想就是将ps->a[ps->sz-1]的位置上的元素置零 //然后对ps->sz进行减一操作。
    //但是事实上仅对size进行——操作就行,但是当size为0的时候就不能进行删除了,//所以要对size为0的极限情况进行限制
    if(ps->size>0)
    
        ps->size--;
    
    //或者用assert 方法
    /*assert(ps->size>0);
    ps->size--;*/

头插函数:

连续存储的所有数据结构的所有的插入函数在插入元素之前,都需要对容量进行判定,以确定是否可以进行插入操作。头插的基本思想与尾插类似,只不过需要对数据元素进行相邻赋值,最后将第一块空间腾出来以进行头插。所有插入函数结束之后,不要忘记对size+1。

void SeqListPushFront(SL*ps,SLDataType x)//头插

    SeqListCheckCapacity(ps);
    //基本思想就是从数组的最后向后移动,依次移动一个单位
    //每移动一次 end指针减一 当end指针为0时,跳出循环
    //此时数组的第一个元素空了出来,我们可以对其进行赋值
    //当插入之后有效容量就会加一,size就要进行加一
    int end=ps->size-1;
    while(end)
    
        ps->a[end+1]=ps->a[end];
        end--;
    
    ps->a[0]=x;
    ps->size++;//头插之后容量变大 size表示的是实际的有效数据的容量


头删函数:

在头删时,要将元素从后向前的覆盖,最终使得size-1。需要注意begin变量的起始位置,这里给出了begin从第二个元素开始的情况。若设置begin为0,那么相应的循环终止条件也要发生改变。

void SeqListPopFront(SL*ps)//头删

    assert(ps->size>0);
    //挪动数据
    int begin=1;
    while(begin< ps->size)
    
        ps->a[begin -1]=ps->a[begin];//向前赋值
        begin++;
    
    ps->size--;

寻找函数:

使用for循环对顺序表元素进行遍历寻找,当所需目标元素在顺序表内时返回其角标,反之返回-1

int SeqListFind(SL* ps,SLDataType x)

    SeqListCheckCapacity(ps);
    int i=ps->size-1;
    for(i=ps->size-1;i>=0;i--)
    
        if(x==ps->a[i])
        
            printf("找到了,为第%d个元素",i);
            return i;
            break;
        
        
    
    return -1;

指定下标插入:

这里采用循环的方式进行空间转换,通过循环将元素相邻从左向右赋值,最终将所指定位置空出,将指定下标的元素插入

void SeqListInsert(*SL ps,int pos,SLDataType x)

    SeqListCheckCapacity(ps);
    assert(pos<ps->size || pos>=0);
    int end=ps->size-1;
    if(pos<ps->size)
    
        int turn=ps->size-pos;//控制跳出循环条件就是插入位置到末尾的距离
        while(turn)
        
            ps->a[end+1]=ps->a[end];
            end--;//end做减法来不断的向前移动进行前后位赋值
            turn--;
        
        ps->a[pos]=x;
        ps->size++;
    
    else if(pos==ps->size)
    
        ps->a[end+1]=ps->a[end];
        ps->a[end]=x;
        ps->size++;
    
    else 
    
        printf("越界插入,错误");
        exit(-1);
    
    

删除指定下标元素:

删除指定下标元素时,给出了第二种指定下标的写法,即从指定下标pos的下一个位置开始进行循环,将pos位置数据进行覆盖,最终size-1。完成指定下标的元素删除

void SeqListErase(SL*ps,int pos)

    assert(pos>=0||pos<ps->size);
    
    int begin=pos+1;
    while(begin<ps->size)
    
        ps->a[begin-1]=ps->a[begin];
        begin++;
    
    ps->size--;

小结

综上,我们对顺序表的结构就有了一个宏观的认识。从逻辑上来说,顺序表本质上是一种数组的变型,即用各种接口函数对顺序表整个动态的数组进行动态维护。从代码实现角度,我们用结构体定义了顺序表的基本内容:用于空间申请的指针、当前存储量size、与整体容量capacity。并定义了多种接口函数用于对顺序表的增删查改。需要注意的是,在结构体实体化时,结构体变量仍是一个变量,若需要对其进行值的修改操作,必须进行传址操作。当完成顺序表存储功能后,需要对其进行销毁,必须要对申请的内存空间进行释放,以避免内存泄漏而产生的不可预知的错误。

希望本文能对你有所帮助☺️。

一文搞懂redis(代码片段)

...ff0c;Redis是发展最快的。作者|一洺来源|阿里技术公众号一什么是NoSQL࿱ 查看详情

一文搞懂数据结构中的线性表(代码片段)

思维导图顺序存储结构查找给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。@TestpublicvoidarrayTest()int[]nums=newint[]-1,0,3,5... 查看详情

一文彻底搞懂cookiesessiontoken到底是什么(代码片段)

点击上方关注“终端研发部”设为“星标”,和你一起掌握更多数据库知识责编:架构君 | 来源:不学无数的程序员链接:https://my.oschina.net/u/4030990/blog/3136476上一篇好文:MySQL数据库的优化,你知道有哪... 查看详情

mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)

文章目录1.缓存的概念1.1.什么是缓存1.2.为什么使用缓存1.3.什么样的数据能使用缓存,什么样的数据不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机... 查看详情

mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)

文章目录1.缓存的概念1.1.什么是缓存1.2.为什么使用缓存1.3.什么样的数据能使用缓存,什么样的数据不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机... 查看详情

mysql一文搞懂mysql语句(基础篇)(代码片段)

MySQL一、数据库的操作1.1显示当前的数据库1.2创建数据库1.3使用数据库1.4删除数据库二、常用数据类型2.1数值类型:2.2字符串类型2.3日期类型三、表的操作3.1创建表3.2查看表结构3.3删除表4.1CRUD4.2新增(Create)(1&#x... 查看详情

mysql一文搞懂mysql语句(基础篇)(代码片段)

MySQL一、数据库的操作1.1显示当前的数据库1.2创建数据库1.3使用数据库1.4删除数据库二、常用数据类型2.1数值类型:2.2字符串类型2.3日期类型三、表的操作3.1创建表3.2查看表结构3.3删除表4.1CRUD4.2新增(Create)(1&#x... 查看详情

一文彻底搞懂leveldb架构(代码片段)

...了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将数据更新到内存中。当内存中的数据量达到一定阈值,将这部分数据再真正刷新到磁盘文... 查看详情

一文彻底搞懂leveldb架构(代码片段)

...了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将数据更新到内存中。当内存中的数据量达到一定阈值,将这部分数据再真正刷新到磁盘文... 查看详情

一文彻底搞懂leveldb架构(代码片段)

...了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将数据更新到内存中。当内存中的数据量达到一定阈值,将这部分数据再真正刷新到磁盘文... 查看详情

一文带你搞懂如何优化慢sql(代码片段)

...对该SQL进行分析和优化的过程中,又重新对SQL语句的执行顺序和SQL语句的执行计划进行了系统性的学习,整理的相关学习和总结如下;作者:京东科技 宋慧超一、前言最近通过SGM监控发现有两个SQL的执行时间占该任务总执行... 查看详情

一文搞懂│什么是跨域?如何解决跨域?(代码片段)

✨目录🎈什么是跨域🎈跨域场景🎈解决跨域的四种方式🎈什么是跨域域:是指浏览器不能执行其他网站的脚本跨域:它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制,所谓同源... 查看详情

一文搞懂rpc原理(代码片段)

RPC原理解析什么是RPCRPC(RemoteProcedureCallProtocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信... 查看详情

一文彻底搞懂zookeeper(代码片段)

....9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeeper从设计模式角度... 查看详情

一文彻底搞懂zookeeper(代码片段)

....9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeeper从设计模式角度... 查看详情

一文带你搞懂rpc到底是个啥(代码片段)

...种网络协议。我们很可能用过HTTP,那么RPC又和HTTP有什么区别呢?RPC还有什么特点,常见的选型有哪些?1.RPC是什么RPC可以分为两部分:用户调用 查看详情

一文彻底搞懂前端沙箱(代码片段)

什么是“沙箱”沙箱(Sandbox)[1]也称作:“沙箱/沙盒/沙盘”。沙箱是一种安全机制,为运行中的程序提供隔离环境。通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用。沙箱能够安全的执行不受信... 查看详情

一文搞懂│mysql的语法操作命令从入门到精通(代码片段)

✨目录🎈初级语法之登录、库操作、表操作、数据操作,索引🎈高级语法之主键、试图、变量、流程、函数、存储过程🎈查询语法之联合查询、子查询、链接查询🎈初级语法之登录、库操作、表操作、数据... 查看详情