手打顺序表:详细讲解顺序表的组成及原理,细节满满!!!

一、线性表

 线性表是n个具有相同特性的数据元素的有限序列。它是一种在实际中使用很广泛的数据结构,在数据结构中常见的线性表有:顺序表、链表、栈、队列……

数据结构分为逻辑结构物理结构

逻辑结构:就是你能在纸上可以画出来的数据结构图形,比如画出来的是按一条线排列的就是线性结构

物理结构:物理结构就是指代再内存中空间存储的方式,连续还是不连续,如果在内存中是以连续的地址存储的话,就叫做线性结构,非连续就叫做非线性结构

所以,线性表在逻辑结构上一定是线性的,但在物理结构上未必就是线性的

二、顺序表

顺序表是一段物理地址连续的存储单位依次存储数据元素的线性结构,则它就是逻辑结构和物理结构都为线性的。

而且,它通常是以数组的形式存储的,因此它的底层结构是数组

那它和数组有什么区别吗?

顺序表是以数组为底层结构,对数组进行各种功能的添加和升级,实现了常见的增删改查等功能,也就是说顺序表就是数组的plus版本。

顺序表又分为两种,静态顺序表和动态顺序表

静态顺序表使用定长数组存储元素,也就是它的存储空间是一开始就设置好的,元素只需要按条件插入即可。

typedef int SLDataType;
#define N 10000
typedef struct SeqList{
    SLDataType arr[N];//定长数组
    int size;//有效元素个数
  }SL;
    

动态顺序表实现动态存储数据元素,可以根据数据元素的多少来进行对空间的申请(按需申请)。

typedef int SLDataType;

typedef struct SeqList{
    SLDataType* arr;//定长数组
    int size;//有效元素个数
    int capacity;//空间容量
  }SL;
    

但是,我们在建立顺序表的时候,都是建立动态顺序表。

原因是:静态顺序表具有空间不确定性,如果一次给的空间太小就会不够用,一次性给的的空间太多了又会造成空间浪费,效率太低了;而动态顺序表则没这个顾虑,它可以通过内存管理实现对空间的按需申请,而且当空间不足时还能实现增容。

动态顺序表的实现

1、初始化

将结构体中指针置空,有效数据个数和空间容量置0。

void SLInit(SL* ps)
{
    ps->arr=NULL;
    ps->size=0;
    ps->capacity=0;
}

我们每实现一个功能就调试一次,这样才能避免写出大量代码后在测试而导致出现问题,无法找到问题。

测试可得

初始化成功。

2、尾插

顾名思义这一步进行从顺序表尾部插入元素,那如何插入呢?

在学习数据结构的过程中,我们要勤画图、会画图,通过图像直接理解数据结构的功能的实现。

由图可知

当进行尾部插入,就是在size的位置插入一个值,然后size再加一,就能实现了。

但在实现插入之前,实现确定顺序表的空间是否足够,当空间不足的时候就需要增容。

接下来就需要写一个增容函数

void SLCheckCapacity(SL* ps)
{
    assert(ps);//ps不能为空
    //当空间不足时
    if(ps->size == ps->capacity)//当两者相等时就表示空间不足就需要扩容
    {
        int newcapacity = ps->capacity==0?4:2*ps->capacity;
    //这里当空间不足时,之所以是扩容两倍,是根据概率论来推算出来的,最好就使用这个倍率
        SLDataType* temp = (SLDataType*)realloc(ps->arr,newcapacity*sizeof(SLDataType));
        if(temp==NULL)//当扩容失败
        {
            perror("capacity Fail!");
            exit(1);
        }
        ps->arr=temp;
        ps->capacity=newcapacity;
     }
}

由这个函数就能轻松写出尾插函数

void SLPushBack(SL* ps,SLDataType x)
{
    assert(ps);
    SLCheckcapacity(ps);
    ps->arr[ps->size++]=x;//将x插入到下标为size的位置,然后size++
}

 测试结果如下:

3、头插 

有尾插就有头插,头插顾名思义就是在顺序表头部插入元,而且也和尾插一样需要进行空间容量的检查,和尾插不同的是它需要进行遍历将头部元素下标位置给让出来,那就需要原顺序表中的元素往后移一位,

画图可得:

 

由图意可得,原元素往后移一位,插入元素放在下表为0的位置 ,然后size++,而后移元素的关系为

这里往后移动元素,则是从后往前移动,由此可以写出代码为

void SLPushFront(SL* ps,SLDatatype x)
{
    assert(ps);
    SLCheckCapacity(ps);
    for(int i=ps->size;i>0;--i)
    {
        ps->arr[i]=ps->arr[i-1];
    }
    ps->arr[0]=x;
    ps->size++;
}

测试结果为 

 

4、尾删 

介绍完插入元素后,我们来了解一下删除。

尾删,顾名思义删除尾部的元素,尾部元素的删除很简单,在已知有效元素个数size的前提下,则最后一元素的下标为size-1,只要进行size--,就可以把最后一元素删除

代码为

void SLPopBack(SL* ps)
{
    assert(ps);
    ps->size--;
}

测试结果为

 5、头删

头删就是将顺序表头部的元素删除,和头插一样需要遍历数组,但头删是将后面的元素往前移,元素的遍历也是从第二个元素开始往前移

画图可得

代码为

void SLPopFront(SL* ps)
{
    assert(ps);
    for(int i=0;i<ps->size-1;++i)
    {
        ps->arr[i+1]=ps->[i];
    }
    ps->size--;
}

 

6、在指定位置前插入元素

这一步比尾插和头插就多了一个指定位置,我们只需要将指定位置后面的元素往后移一位,在将size++,但在插入元素之前,我们还需要判断插入位置的是否合法,也就是它不能超过顺序表的范围,指定位置可以是size

画图可得

代码为

void SLInsert(SL * ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos>=0&&pos<=ps->size);
    for(int i=ps->size;i>pos;--i)
    {
        ps->arr[i]=ps->arr[i-1];
    }
    ps->arr[pos]=x;
    ps->size++;
}

测试结果为

7、删除指定位置元素

这一步与上一步一样也需要检查插入位置是否合法,但它的指定位置不能是size,其他的地方和头删没什么区别

画图可得

 代码为

void SLErase(SL* ps,int pos)
{
    assert(ps);
    assert(pos>=0&&pos<ps->size);
    for(int i=pos;i<ps->size-1;++i)
    {
        ps->arr[i]=ps->arr[i+1];
    }
    ps->size--;
}

测试结果为

8、 找寻元素

找寻元素很简单,就是遍历顺序表,判断表里元素是否有和想要找寻的元素相同的,有则返回它的下标,没有的话就返回小于0的数

代码为

int SLFind(SL* ps,SLDataType x)
{
    assert(ps);
    for(int i=0;i<ps->size;++i)
    {
        if(ps->arr[i]==x)
        {
            return i;
        }
    }
    return -1;
}

测试结果如下

9、销毁顺序表

当顺序表使用完以后 ,为了避免空间的浪费就需要将顺序表给销毁。销毁的步骤就只需要记住这句话,初始化了声明就销毁什么,但要注意的是,在销毁前要判断一下顺序表结构中数组是否为空,如果不为空就置空。

代码为

void SLDestroy(SL* ps)//销毁
{
    assert(ps);
    if (ps->arr)
    {
        ps->arr = NULL;
    }
    ps->size = ps->capacity = 0;
}

运行结果为 

结尾

就此我们首先了顺序表所有的基本功能 ,若对你学习顺序表有帮助的话,给小编三连哈!多谢!多谢!

评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值