顺序表详解

何为顺序表?

如果用一组连续的存储单元依次存放线性表中的所有元素,则称该类型的线性表为顺序表。其特点是元素的存储位置与逻辑位置一一对应,因此顺序表通常用数组来表示

顺序表的定义:

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

  • 缺陷:给小了不够用,给大了可能浪费,非常不实用
#define N 100
typedef int sldatatype;    //后续要存储其它类型时方便更改
typedef struct slNode{
    sldatatype a[N]; //定长顺序表
    size_t len; //顺序表的长度(元素个数)
}seqlist; 

2、动态顺序表:使用动态开辟的数组存储元素(以下操作用动态顺序表做)

  • 动态顺序表可根据我们的需要分配空间大小
  • len 表示当前顺序表中已存放的数据个数
  • capacity 表示顺序表总共能够存放的数据个数
    typedef struct slNode{
        sldatatype *a;  //指向动态开辟的数组
        size_t len;     //数据个数
        size_t capacity;//容量大小
    }seqlist;

顺序表的初始化: 

void seqlistInit(seqlist*sl)
{
    assert(sl!=NULL);//断言,防止传进来的指针为空
/*
    if(sl==NULL){
        cout<<"您的顺序表为空"<<endl;
        break;
    };
*/    
    sl->a==NULL; //初始顺序表为空
    sl.len=0;    //初始数据个数为0
    sl.capacity=0;    //初始空间容量为0
}

顺序表的销毁释放:

void seqlistDestroy(seqlist *sl)
{
    assert(sl!=NULL);//断言
    
    free(sl->a);     //释放动态开辟的空间
    sl->a = NULL;    //置空
    sl->len = 0;     
    sl->capacity = 0; 
}

 检查顺序表容量是否已满,进行增容

为什么不采取插一个数据,增容一个空间的方式呢,因为这样也太麻烦了,代价也太大了,一般情况下,为了避免频繁的增容,当空间满了后,我们不会一个一个的去增,而是一次增容 2 倍,当然也不会一次增容太大,比如 3 倍 4 倍,空间可能会浪费,2 倍是一个折中的选择。

void checkCapacity(seqlist *sl)
{
    assert(sl!=NULL);

    if(sl->len==sl->capacity)
    {
        size_t newCapacity;
        if(sl->capacity==0)
            newCapacity = sl->capacity =4;
        else
            newCapacity = sl->capacity * 2;

        //因为sl->a不能直接开辟,因此借助于p进行开辟
        sldatatype *p = (sldatatype *)realloc(sl->a,newCapacity*sizeof(sldatatype));
        if(p==NULL)
        {
            perror("realloc出错");
            exit(-1);
        }
        sl->a = p; //p不为空,开辟成功
        sl->capacity = newcapacity; //更新容量
    }
}

顺序表尾插

void seqlistPushback(seqlist* sl, sldatatype x)
{
    //判断是否为空
    assert(sl != NULL);
    //判断是否容量已满,若满进行开辟
    checkCapacity(sl);

    sl->a[sl->len] = x; //尾插数据
    sl->len++;
}

 顺序表尾删

void seqlistPopback(seqlist *sl)
{
    assert(sl !=NULL);
    assert(sl->len != 0);

    sl->len--; //有效数据-1
    //不知道sldatatype是什么类型的数据,不能冒然的赋值为0
	//psl->a[psl->size - 1] = 0;
}

 顺序表头插

void seqlistPushfront(seqlist* sl , sldatatype x)
{
    assert(sl != NULL);
    checkCapacity(sl);

    //错误代码
    //错误分析,思路:将i位置的值赋给i+1位置的值:。
    //第一次后,i的值假设为5,则i+1的值为5,之后就将5往后传递,从而后面值都为5
    /*int i = 0; 
    for (; i < sl->len; i++)
    {
        sl->a[i + 1] = sl->a[i];
    }*/
    
    int i = 0;
    for (i = sl->len; i > 0; i--) {
        sl->a[i] = sl->a[i - 1];
    }

    sl->a[0] = x;
    sl->len++;      
}

 顺序表头删

void seqlistPopfront1(seqlist* sl) {
    assert(sl != NULL);
    assert(sl->len != 0);

    for (int i = 1; i < sl->len; i++) {
        sl->a[i - 1] = sl->a[i];
    }
    sl->len--;
}

 打印顺序表

void seqlistPrint(const seqlist* sl)
{
    assert(sl != NULL);

    if (sl->len == 0)
    {
        cout << "顺序表为空" << endl;
        return;
    }
    else {
        int i = 0;
        for (; i < sl->len; i++)
        {
            cout << sl->a[i] << endl;
        }
        cout << endl;
        return;
    }
}

 在顺序表中查找指定值

int seqlistFind(const seqlist* sl,sldatatype x)
{
    assert(sl != NULL);

    for (int i = 0; i < sl->len; i++) {
        if (sl->a[i] == x)
            return i;
    }
    return -1;
}

 在顺序表指定下标位置插入数据

void seqlistInsert(seqlist* sl, size_t pos,sldatatype x)
{
    assert(sl!=NULL);  //断言
    assert(pos >0 && pos <= sl->len+1);  //检查pos下标的合法性

    checkCapacity(sl);  //检查顺序表容量是否已满

    size_t i = 0;
    for (i = sl->len; i >= pos; i--)  //将pos位置后面的数据依次向后挪动一位
    {
        sl->a[i] = sl->a[i - 1];
    }
    sl->a[pos-1] = x;  //插入数据
    sl->len++;  //有效数据个数+1
}


//头插改进
void seqlistPushfront2(seqlist* sl,sldatatype x)
{
    seqlistInsert(sl, 1, x);
}

 在顺序表中删除指定下标位置的数据

void seqlistErase(seqlist* sl, size_t pos)
{
    assert(sl);  //断言
    assert(sl->len > 0);  //顺序表不能为空
    assert(pos > 0 && pos <= sl->len);  //检查pos下标的合法性

    size_t i = 0;
    for (i = pos ; i <= sl->len; i++)  //将pos位置后面的数据依次向前挪动一位
    {
        sl->a[i - 1] = sl->a[i];
    }
    sl->len--;  //有效数据个数-1
}

 查看顺序表中有效数据个数

可能大家会有一个疑问,我在主函数里面直接通过定义的结构体变量直接访问就好了呀,为啥还要弄一个函数嘞?

在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况。

但越界不一定报错,系统对越界的检查是一种抽查

  • 越界读一般是检查不出来的

  • 越界写如果是修改到标志位才会检查出来

(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)

size_t seqlistLen(const seqlist* sl)
{
    assert(sl);  //断言

    return sl->len;
}

修改指定下标位置的数据 

void seqlistModify(seqlist* sl, size_t pos, sldatatype x)
{
    assert(sl);  //断言
    assert(sl->len > 0);  //顺序表不能为空
    assert(pos > 0 && pos <= sl->len);  //检查pos下标的合法性

    sl->a[pos-1] = x;  //修改pos下标处对应的数据
}

 完整代码:(vs2022运行成功)

#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
#define N 100
typedef int sldatatype;    //后续要存储其它类型时方便更改

typedef struct slNode {
    sldatatype* a;  //指向动态开辟的数组
    size_t len;     //数据个数
    size_t capacity;//容量大小
}seqlist;

//检查顺序表容量是否满了,好进行增容
void checkCapacity(seqlist* sl)
{
    assert(sl != NULL);

    if (sl->len == sl->capacity)
    {
        size_t newCapacity;
        if (sl->capacity == 0)
            newCapacity = sl->capacity = 4;
        else
            newCapacity = sl->capacity * 2;

        //因为sl->a不能直接开辟,因此借助于p进行开辟
        //realloc包含了已有的数据信息,会进行拷贝
        sldatatype* p = (sldatatype*)realloc(sl->a, newCapacity * sizeof(sldatatype)); 
        if (p == NULL)
        {
            perror("realloc出错");
            exit(-1);
        }
        sl->a = p; //p不为空,开辟成功
        sl->capacity = newCapacity; //更新容量
    }
}

//初始化顺序表
void seqlistInit(seqlist* sl)
{
    assert(sl != NULL);//断言,防止传进来的指针为空
/*
    if(sl==NULL){
        cout<<"您的顺序表为空"<<endl;
        break;
    };
*/
    sl->a = NULL; //初始顺序表为空
    sl->len = 0;    //初始数据个数为0
    sl->capacity = 0;    //初始空间容量为0
}

//销毁(释放)顺序表
void seqlistDestroy(seqlist* sl)
{
    assert(sl != NULL);//断言

    free(sl->a);     //释放动态开辟的空间
    sl->a = NULL;    //置空
    sl->len = 0;
    sl->capacity = 0;
}

//顺序表尾插
void seqlistPushback(seqlist* sl, sldatatype x)
{
    //判断是否为空
    assert(sl != NULL);
    //判断是否容量已满,若满进行开辟
    checkCapacity(sl);

    sl->a[sl->len] = x; //尾插数据
    sl->len++;
}

//顺序表打印
void seqlistPrint(const seqlist* sl)
{
    assert(sl != NULL);

    if (sl->len == 0)
    {
        cout << "顺序表为空" << endl;
        return;
    }
    else {
        int i = 0;
        for (; i < sl->len; i++)
        {
            cout << sl->a[i] << endl;
        }
        cout << endl;
        return;
    }
}

//顺序表尾删
void seqlistPopback(seqlist* sl)
{
    assert(sl != NULL);
    assert(sl->len != 0);

    sl->len--; //有效数据-1
    //不知道sldatatype是什么类型的数据,不能冒然的赋值为0
    //psl->a[psl->size - 1] = 0;
}

//顺序表头插1
void seqlistPushfront1(seqlist* sl, sldatatype x)
{
    assert(sl != NULL);
    checkCapacity(sl);

    //错误代码
    //错误分析,思路:将i位置的值赋给i+1位置的值:。
    //第一次后,i的值假设为5,则i+1的值为5,之后就将5往后传递,从而后面值都为5
    /*int i = 0;
    for (; i < sl->len; i++)
    {
        sl->a[i + 1] = sl->a[i];
    }*/

    int i = 0;
    for (i = sl->len; i > 0; i--) {
        sl->a[i] = sl->a[i - 1];
    }

    sl->a[0] = x;
    sl->len++;
}

//顺序表头删1
void seqlistPopfront1(seqlist* sl) {
    assert(sl != NULL);
    assert(sl->len != 0);

    for (int i = 1; i < sl->len; i++) {
        sl->a[i - 1] = sl->a[i];
    }
    sl->len--;
}

//在顺序表中查找指定值,删除指定值,在此基础上进行操作
int seqlistFind(const seqlist* sl, sldatatype x)
{
    assert(sl != NULL);

    for (int i = 0; i < sl->len; i++) {
        if (sl->a[i] == x)
            return i;
    }
    return -1;
}

//在顺序表指定下标位置插入数据,可进行头插和尾插
void seqlistInsert(seqlist* sl, size_t pos, sldatatype x)
{
    assert(sl != NULL);  //断言
    assert(pos > 0 && pos <= sl->len + 1);  //检查pos下标的合法性

    checkCapacity(sl);  //检查顺序表容量是否已满

    size_t i = 0;
    for (i = sl->len; i >= pos; i--)  //将pos位置后面的数据依次向后挪动一位
    {
        sl->a[i] = sl->a[i - 1];
    }
    sl->a[pos - 1] = x;  //插入数据
    sl->len++;  //有效数据个数+1
}

//头插改进2
void seqlistPushfront2(seqlist* sl, sldatatype x)
{
    seqlistInsert(sl, 1, x);
}

//在顺序表中删除指定下标位置的数据
void seqlistErase(seqlist* sl, size_t pos)
{
    assert(sl);  //断言
    assert(sl->len > 0);  //顺序表不能为空
    assert(pos > 0 && pos <= sl->len);  //检查pos下标的合法性

    size_t i = 0;
    for (i = pos; i <= sl->len; i++)  //将pos位置后面的数据依次向前挪动一位
    {
        sl->a[i - 1] = sl->a[i];
    }
    sl->len--;  //有效数据个数-1
}

//顺序表头删2
void seqlistPopfront2(seqlist* sl)
{
    seqlistErase(sl, 0);  //改造头删接口
}

//查看顺序表中有效数据个数
size_t seqlistLen(const seqlist* sl)
{
    assert(sl);  //断言

    return sl->len;
}

//修改指定下标位置的数据
void seqlistModify(seqlist* sl, size_t pos, sldatatype x)
{
    assert(sl);  //断言
    assert(sl->len > 0);  //顺序表不能为空
    assert(pos > 0 && pos <= sl->len);  //检查pos下标的合法性

    sl->a[pos - 1] = x;  //修改pos下标处对应的数据
}


int main()
{
    seqlist s1;
    seqlistInit(&s1);
    //尾插数据
    seqlistPushback(&s1, 5);
    seqlistPushback(&s1, 3);
    seqlistPushback(&s1, 4);
    seqlistPushback(&s1, 6);
    seqlistPushback(&s1, 8);
    seqlistPushback(&s1, 0);
    seqlistPrint(&s1);

    //尾删除
    seqlistPopback(&s1);
    seqlistPrint(&s1);

    //头插
    seqlistPushfront1(&s1, 101);
    seqlistPrint(&s1);

    //头删
    seqlistPopfront1(&s1);
    seqlistPopfront1(&s1);
    seqlistPrint(&s1);

    //查询指定元素
    cout << "查询5元素的位置:" << seqlistFind(&s1, 5) << endl;
    cout << endl;

    //指定位置插入元素
    seqlistInsert(&s1, 3, 666);
    seqlistPrint(&s1);

    //在顺序表中删除指定下标位置的数据
    seqlistErase(&s1, 3);
    seqlistPrint(&s1);

    //查看顺序表中有效数据个数
    cout << "查看顺序表中有效数据个数:" << seqlistLen(&s1) << endl << endl;

    //修改指定下标位置的数据
    seqlistModify(&s1, 2, 999);
    seqlistPrint(&s1);

    //指定位置插入元素,可以进行尾插
    seqlistInsert(&s1, 5, 666);
    seqlistPrint(&s1);
}

参考:

1.(1条消息) C++中的assert用法_喜欢吃冰棍de谷利文君的博客-CSDN博客_c++ assert

2. 大部分以这篇文章为核心,有些小地方进行了改进(基本关于postion的问题)(1条消息) 【数据结构入门】顺序表(SeqList)详解(初始化、增、删、查、改)_CodeWinter的博客-CSDN博客_seqlist

3.(1条消息) 动态内存分布——malloc,calloc,realloc,free的使用。以及关于动态内存的常见错误。_我的代码爱吃辣的博客-CSDN博客 

4.(1条消息) c++编程之perror()_kangshuangzhu的博客-CSDN博客_c++ perror 

5.(c++建议还是使用visual studio)vscode配置C/C++环境(超级详细)Day2022/12/02_DreamSuif的博客-CSDN博客_vscode配置c/c++环境 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小羽儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值