【顺序表】

一、顺序表简介

   顺序表是在计算机内存中以数组形式保存的线性表。通俗来说,对于顺序表的增删查改等操作就是对于数组进行增删查改。
   一般对于数组来说有静态数组也有动态数组,即相对应的也有静态顺序表和动态顺序表。(本篇文章以动态顺序表来举例)

1)静态顺序表

   静态顺序表即在顺序表定义的时候就指定了顺序表的固定空间大小,不可扩增空间。

#include <stdio.h>
#include <stdlib.h>
#define SIZE 1000  //定义顺序表大小为SIZE大小

typedef int SLDataType;//将顺序表数据类型int定义为SLDataType
typedef struct SepList
{
	  SLDataType _data[SIZE]; //为顺序表开辟开辟SIZE大小的空间
	  int _size;           //_size表示顺序表中存储的数据个数 	                                                                                                                                     
}SL;  //定义顺序表并起别名为SL

2)动态顺序表

  动态顺序表,顾名思义顺序表的大小可重定义,根据应用的环境不同随时说明其维数大小。

#include <stdio.h>
#include <stdlib.h>

typedef int SLDataType;  //将顺序表数据类型int定义为SLDataType
typedef struct SepList 
{
  SLDataType* _data;		//动态顺序表指针,大小可变
  int _size;				//定义_size表示顺序表数据个数
  int _capacity;		    //定义_capacity为顺序表容量大小
}SL;	//定义顺序表并起别名为SL

二、顺序表初始化

void SLInit(SL* ps)//顺序表初始化
{
  if(NULL == ps)
  {
    perror("SepList Error Defined!");
    exit(1);
  }
  ps->_data = NULL;  //初始化数组空间为空
  ps->_size = 0;     //初始化有效数据个数为0
  ps->_capacity = 0; //初始化数据空间大小0
}

在这里插入图片描述
  顺序表初始化是为了构造一个空的顺序表,初始化数据,以防定义时的计算机赋予的随机值干扰后续操作,并检测顺序表是否成功定义。

三、顺序表插入数据

1)顺序表尾插

void SLPushBack(SL* ps, SLDataType x)//顺序表尾插
{
    assert(ps);

    SLCheckCapacity(ps);//检查顺序表空间是否足够,不够就开辟新的空间
    ps->_data[ps->_size++] = x;//尾部写入数据
}

  如下图所示,顺序表尾插就是在已开辟好的数组空间末尾写入要插入的数据。在函数SLPushBack接口中,assert断言检验顺序表是否成功创建后,会经过SLCheckCapacity函数接口检验顺序表容量是否足够,若不足够就开辟新的空间用以存储数据,后续会介绍到如何开辟空间,最后再在顺序表中写入数据。
  其中,顺序表SL中ps->_size代表顺序表数组中已存储的数据个数,根据数组下标的特性,它的数值指向数组最后数据的下一个坐标(如此做法是为了方便写入数据),如,当ps->_size为0时,它指向数组第0位,同时也代表了数组中数据个数为0;如下图,在数字4还没有写入时,ps->_size的值为3,代表了数据个数为3,同时又指向数组末尾数据下一个空间,此时要想插入数据就可利用ps->_data[ps->_size]直接访问这个空间,执行完成后ps->_size++结果为4,结果解释同理。
在这里插入图片描述

2)顺序表头插

void SLPushFront(SL* ps, SLDataType x)//顺序表头插
{
    assert(ps);//检验顺序表是否定义成功
    SLCheckCapacity(ps);//检查空间是否足够,不够就重新开辟
	
	//定义end从后往前访问数组,在end + 1位置写入数据
    int end = ps->_size - 1;
    while (end >= 0)
    {
        ps->_data[end + 1] = x;
        end--;
    }
    ps->_data[0] = x;//数组头部写入数据
    ps->_size++;//顺序表内部存储的数据个数加1
}

  如下图所示,顺序表头插其原理就是将整个数组的数据从后往前挪动一位,挪动完成后,再利用ps->_data[0]直接在头部写入数据。
在这里插入图片描述

3)pos位置插入数据

void SLInsert(SL* ps, int pos, SLDataType x)//pos位置插入数据
{
    assert(ps);//检验顺序表是否定义成功
    assert(pos >= 0 && pos <= ps->_size);//数据插入位置需在包括数组开头和末尾这个区间之间

    SLCheckCapacity(ps);//检查空间是否足够,不够就重新开辟
    int end = ps->_size - 1;//定义end从后往前访问数组,在end + 1位置写入数据
    while (end >= pos)//数据挪动,挪动完至pos位置后停止挪动
    {
        ps->_data[end + 1] = ps->_data[end];
        end--;
    }
    ps->_data[pos] = x;//pos位置写入数据
    ps->_size++;//顺序表内部存储的数据个数加1
}

  如下图所示,顺序表任意位置插入数据其原理同顺序表头插类似,只是将头部固定位置换成了任意位置。
在这里插入图片描述
  根据此接口原理,可以将顺序表头插以及尾插更换如下:

//顺序表头插
void SLPushFront(SL* ps, SLDataType x)//顺序表头插
{
	SLInsert(ps, 0, x);//复用SLInsert接口在头部插入数据
}
//顺序表尾插
void SLPushBack(SL* ps, SLDataType x)//顺序表尾插
{
    SLInsert(ps, ps->_size, x);//复用SLInsert接口在尾部插入数据
}

4)动态开辟空间

void SLCheckCapacity(SL* ps)//动态开辟  
{
	assert(ps);//检验顺序表是否定义成功
	
    if (ps->_size == ps->_capacity)//如果已存储数据个数等于已开辟的空间容量,执行后续代码,开辟新的空间
    {
    	//设置顺序表容量(newCapacity)的大小
    	//若容量为0,则设置容量空间大小为4(本篇数据类型为int,则容量4代表 4 * 4 byte大小空间)
    	//若容量不为0,则设置容量大小为原本容量大小的两倍
        int newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
        //开辟newCapacity大小空间(开辟空间使用realloc接口)
        SLDataType* newData = (SLDataType*)realloc(ps->_data, newCapacity * sizeof(SLDataType));
        if (NULL == newData)
        {
            perror("Realloc Fail!");
            exit(-1);
        }
        //将新开辟的空间地址交给顺序表
        ps->_data = newData;
        //将新的容量大小告诉顺序表
        ps->_capacity = newCapacity;
    }
}

  具体如何开辟空间由上述代码注释部分可给出,下面举两个例子观看经过SLCheckCapacity接口后开辟空间以及存储的数据在存储空间中位置的变化:

1> 例一:SLCheckCapacity内部执行

在这里插入图片描述
  如上图所示,此状态为顺序第一次访问SLCheckCapacity接口,其中_data存储的地址为空。
在这里插入图片描述
  由后续代码执行完成后,由上图可见,_data指针变量存储的地址为0x0000027F89420460(图中有些小瑕疵,红框框错了范围,已由蓝线标出),一个int 类型大小为4byte,所以开辟出来能存储4个int大小的数据的空间为4 * 4 (16)byte的连续空间,由图可见,这些空间已经被赋随机初始值0xcdcdcdcd(VS在debug环境下,一般将内存填充为0xcccccccc或0xcdcdcdcd)。

2> 例二:SLCheckCapacity外部执行

#include<stdio.h>
#include"SepList.h"

//测试用例
int main()
{
	SL s;

	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);

	SLPushFront(&s, 100);
	SLPushFront(&s, 99);
	SLPushFront(&s, 98);
	SLPushFront(&s, 97);
	SLPrint(&s);
	
	SLInsert(&s, 0, 10);
	SLPrint(&s);

	SLInsert(&s, s._size, 20);
	SLPrint(&s);

	return 0;
}

图2-1
  由上图,此时可见顺序表执行初始化后空间未开辟状态。
图2-2
  由上图,执行完成第一步SLPushBack,可见在空间中的地址0x000001AC592D0280处写入了想要尾插的数据0x00000001(内存中以16进制存储,具体空间开辟过程参考SLCheckCapacity接口代码与上述例一),这种数据低位存储在低地址处的方法为小端法。
在这里插入图片描述
  由上图,当执行完所有SLPushBack时候,可见所有尾插数据都依次存储在开辟出来的连续空间中。
在这里插入图片描述
  由上图,可见执行完所有SLPushFront后空间种的存储,图中插入数据时空间不够又重新开辟了空间,可见_data为0x000001AC592D0A70,及此地址后4 * 8 byte连续空间。
图2-4
  由上图,复用SLInsert头插数据,空间不够重新开辟二倍大小空间,此时_data为0x000001AC592CA000。
图2-5
  由上图,复用SLInsert尾插数据(容量足够,无需开辟空间),可见空间中数据的存储位置以及结果打印。
  注意:上述例子在开辟空间时,_data中存储的地址经过三次扩容就发生了三次变化,为什么不能不变呢?这个涉及到realloc的扩容机制(原地扩容与异地扩容,本篇文章暂不提及,感兴趣的可以去查查资料)。

四、顺序表删除数据

1)顺序表尾删

void SLPopBack(SL* ps)//顺序表尾删
{
    assert(ps);//检验顺序表是否定义成功

    if (ps->_size == 0) return;//如果顺序表中数据个数为空,则直接返回
    ps->_size--;			   //否则,ps->_size - 1
}

  顺序表尾删数据,说白了就是让顺序表存储的数据个数(_size)减1,由上述代码和下图可见,即直接让ps->_size - 1即可(因为顺序表是通过_size知道顺序表有多少个数据以及由此来限制顺序表的访问范围,其值减1后就如同告诉我们:原本最后的空间不能再访问,那里没有存储数据)。
在这里插入图片描述

2)顺序表头删

void SLPopFront(SL* ps)//顺序表头删
{
    assert(ps);//检验顺序表是否定义成功

	//数据挪动
    int begin = 1;
    while (begin < ps->_size)
    {
        ps->_data[begin - 1] = ps->_data[begin];
        begin--;
    }
    ps->_size--;
}

  顺序表头删,其基本操作就是将第0位之后的数据统统往前挪动一位,由下图可见,数据挪动完,将ps->_size的值减1即可,原本最后一位的5无需再管它(同顺序表尾删),ps->_size限制了顺序表的访问范围。
在这里插入图片描述

3)删除pos位置数据

void SLErase(SL* ps, int pos)//删除pos位置数据
{
    assert(ps);//检验顺序表是否定义成功
    assert(pos >= 0 && pos < ps->_size);//能够删除的数据位置在数组下标范围为[0,ps->_size - 1]中

	//数据挪动
    int begin = pos + 1;
    while (begin < ps->_size)
    {
        ps->_data[begin - 1] = ps->_data[begin];
        begin++;
    }
    ps->_size--;
}

  由下图可见,删除pos位置数据操作类似顺序表头删,不做过多解释。
在这里插入图片描述

  根据此接口原理,可以将顺序表头删以及尾删更换如下:

//顺序表头删
void SLPopFront(SL* ps)//顺序表头删
{
	SLErase(ps, 0);//复用SLErase接口
}
//顺序表尾删
void SLPopBack(SL* ps)//顺序表尾删
{
	SLErase(ps, ps->_size - 1);//复用SLErase接口
}

五、查找数据

//从begin位置开始寻找x
//寻找到了返回x位置
//失败返回-1
int SLFind(SL* ps, SLDataType x, int begin)
{
    assert(ps);
    assert(begin >= 0 && begin < ps->_size);

    for (int i = begin; i < ps->_size; i++)
    {
        if (x == ps->_data[i])
        {
            return i;
        }
    }
    return -1;
}

六、打印顺序表

void SLPrint(SL* ps)//打印顺序表
{
    assert(ps);

    for (int i = 0; i < ps->_size; i++)
    {
        printf("%d ", ps->_data[i]);
    }
    printf("\n");
}

七、销毁顺序表

void SLDestroy(SL* ps)//销毁顺序表
{
    assert(ps);

    if (ps->_data)
    {
        free(ps->_data);//释放申请的空间
        ps->_data = NULL;//将指针变量置空
        ps->_capacity = ps->_size = 0;//将容量以及数据个数置0
    }
}
//销毁顺序表是为了防止内存泄漏,不能对于不再使用的内存置之不理

八、总览

1)SepList.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType;
typedef struct SepList
{
	SLDataType* _data;
	int _size;
	int _capacity;
}SL;

void SLInit(SL* ps);//顺序表初始化,ps(point to SL) 

void SLCheckCapacity(SL* ps);//动态开辟

void SLPrint(SL* ps);//打印顺序表

void SLDestroy(SL* ps);//销毁顺序表

void SLPushBack(SL* ps, SLDataType x);//顺序表尾插

void SLPopBack(SL* ps);//顺序表尾删

void SLPushFront(SL* ps, SLDataType x);//顺序表头插

void SLPopFront(SL* ps);//顺序表头删 

void SLInsert(SL* ps, int pos, SLDataType x);//pos位置插入数据

void SLErase(SL* ps, int pos);//删除pos位置数据

int SLFind(SL* ps, SLDataType x, int begin);//查找数据

2)SepList.c

#include<assert.h>
#include "SepList.h"

void SLInit(SL* ps)//顺序表初始化
{
    if (NULL == ps)
    {
        perror("SepList Error Defined!");
        exit(1);
    }
    ps->_data = NULL;  //初始化数组指针为空
    ps->_size = 0;     //初始化数据个数为0
    ps->_capacity = 0; //初始化数据空间大小0
}


void SLCheckCapacity(SL* ps)//动态开辟  
{
    if (NULL == ps)
    {
        perror("SepList Error Defined!");
        exit(1);
    }
    if (ps->_size == ps->_capacity)
    {
        int newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
        SLDataType* newData = (SLDataType*)realloc(ps->_data, newCapacity * sizeof(SLDataType));
        if (NULL == newData)
        {
            perror("Realloc Fail!");
            exit(-1);
        }
        ps->_data = newData;
        ps->_capacity = newCapacity;
    }
}


void SLPrint(SL* ps)//打印顺序表
{
    assert(ps);

    for (int i = 0; i < ps->_size; i++)
    {
        printf("%d ", ps->_data[i]);
    }
    printf("\n");
}


void SLDestroy(SL* ps)//销毁顺序表
{
    assert(ps);

    if (ps->_data)
    {
        free(ps->_data);
        ps->_data = NULL;
        ps->_capacity = ps->_size = 0;
    }
}


void SLPushBack(SL* ps, SLDataType x)//顺序表尾插
{
    assert(ps);

    SLCheckCapacity(ps);
    ps->_data[ps->_size++] = x;
}


void SLPopBack(SL* ps)//顺序表尾删
{
    assert(ps);

    if (ps->_size == 0) return;
    ps->_size--;
}

void SLPushFront(SL* ps, SLDataType x)//顺序表头插
{
    assert(ps);
    SLCheckCapacity(ps);

    int end = ps->_size - 1;
    while (end >= 0)
    {
        ps->_data[end + 1] = x;
        end--;
    }
    ps->_data[end] = x;
    ps->_size++;
}


void SLPopFront(SL* ps)//顺序表头删
{
    assert(ps);

    int begin = 1;
    while (begin < ps->_size)
    {
        ps->_data[begin - 1] = ps->_data[begin];
        begin--;
    }
    ps->_size--;
}


void SLInsert(SL* ps, int pos, SLDataType x)//pos位置插入数据
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->_size);

    SLCheckCapacity(ps);
    int end = ps->_size - 1;
    while (end >= pos)
    {
        ps->_data[end + 1] = ps->_data[end];
        end--;
    }
    ps->_data[pos] = x;
    ps->_size++;
}


void SLErase(SL* ps, int pos)//删除pos位置数据
{
    assert(ps);
    assert(pos >= 0 && pos < ps->_size);

    int begin = pos + 1;
    while (begin < ps->_size)
    {
        ps->_data[begin - 1] = ps->_data[begin];
        begin++;
    }
    ps->_size--;
}


//从begin位置开始寻找x
//寻找到了返回x位置
//失败返回-1
int SLFind(SL* ps, SLDataType x, int begin)
{
    assert(ps);
    assert(begin >= 0 && begin < ps->_size);

    for (int i = begin; i < ps->_size; i++)
    {
        if (x == ps->_data[i])
        {
            return i;
        }
    }
    return -1;
}

九、C++顺序表草稿

  此部分仅为C++顺序表草稿,后续会再更改。
  C++顺序表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值