• 顺序表(第二节)实现和解析


    目录

    1.顺序表中的头文件 (每一种函数方法)

     2.关于typedef 的用法

    3.初始化和销毁表

    3.1初始化表

    3.2销毁表 

    4.打印表

    5.自动扩容表!!!(重点)

    6.头部插入表和尾部插入表

    6.1尾部插入表 

    6.2头部插入表 

    7.头部删除表和尾部删除表

    7.1判断表是否为空

    7.2尾部删除表

    7.3头部删除表

    8. 指定位置的插入和删除

    8.1指定位置的插入

    8.2指定位置的删除 

    大家有什么不懂的可以私信问我,我也是刚学会!! 


    1.顺序表中的头文件 (每一种函数方法)

    1. #define INIT_CAPACITY 4
    2. typedef int SLDataType;
    3. // 动态顺序表 -- 按需申请
    4. typedef struct SeqList
    5. {
    6.     SLDataType* a;
    7.     int size; // 有效数据个数
    8.     int capacity; // 空间容量
    9. }SL;
    10. //初始化和销毁
    11. void SLInit(SL* ps);
    12. void SLDestroy(SL* ps);
    13. void SLPrint(SL* ps);
    14. //扩容
    15. void SLCheckCapacity(SL* ps);
    16. //头部插⼊删除 / 尾部插⼊删除
    17. void SLPushBack(SL* ps, SLDataType x);
    18. void SLPopBack(SL* ps);
    19. void SLPushFront(SL* ps, SLDataType x);
    20. void SLPopFront(SL* ps);
    21. //指定位置之前插⼊/删除数据
    22. void SLInsert(SL* ps, int pos, SLDataType x);
    23. void SLErase(SL* ps, int pos);

    我的讲述方法是对这几行代码一一进行讲解,建议复制一份在VS中,边看博客边看头文件,不然真的看不懂。

     2.关于typedef 的用法

    1.typedef用来给类型重命名,这里为什么要把int 重命名改为SLDataType呢?

    因为如果用int的话后续的代码如果要更改,那就得把全部的int 都更改,如果提前typedef了就可以直接改 上面的类型就好了

    2.下面把struct SeqList 改为SL仅仅是因为前面那一串太长了,给他缩短一点,增加代码的可读性

    3.初始化和销毁表

    3.1初始化表

     初始化表,用于初始化变量。变量的使用前都是要初始化的。

    a的指针的意思是指向 要填充的变量

    capacity的意思是目前开辟的内存的量,size表示现在被占用的内存的量

    1. void SLInit(SL* ps)
    2. {
    3. ps->a = NULL;
    4. ps->capacity = ps->size = 0;
    5. }

    3.2销毁表 

    判断ps->a的原因是,判断这个指针是不是为空,如果为空,会不执行任何操作直接返回。

    然后再给ps a 置空,capacity和size 置为0;

    1. void SLDestory(SL* ps)
    2. {
    3. if (ps->a)
    4. {
    5. free(ps->a);
    6. }
    7. ps->a = NULL;
    8. ps->capacity = ps->size = 0;
    9. }

    4.打印表

     打印表很简单,就用for循环遍历 realloc开辟的空间就好了(在下一部分自动扩容表讲解)

    注意打印结束后换行,避免下次打印连在一起。

    ps->a[ i ] 的意思就和数组一样。用来表示每一块开辟空间里的存储的值。

    1. void SLPrint(SL* ps)
    2. {
    3. for (int i = 0; i < ps->size; i++)
    4. {
    5. printf("%d ", ps->a[i]);
    6. }
    7. printf("\n");
    8. }

    5.自动扩容表!!!(重点)

    1. 首先扩容表的前提是,你的capacity(现在总共开辟的内存量)和size(已有数据的内存)这两个变量已经相等,说明这时候的内存已经不够了,需要添加。

    2.所以判断语句是 ps capacity == ps size(这里不写箭头了)。

    3.看到下面的int newcapacity 其实是用来判断 ps->capacity是否为0.如果为0则就直接等于4

    不等于就等于 2 * ps->capacity, (0 ?4 :2*ps capacity就是判断是否为0,真就为前,假就为后)。

    4.为什么要判断是否为0呢?因为如果为0,你下面开辟的代码 realloc开辟出来的空间还是0

    5.之和在判断是否为NULL开辟失败的情况。

    6.最后将开辟的空间 再次赋予 ps a。

    7.ps capacity = newcapacity。

    1. void SLCheckCapacity(SL* ps)
    2. {
    3. if (ps->capacity == ps->size)
    4. {
    5. /*if (ps->capacity == 0) 判断capacity的情况,和下面newcapacity意思相同
    6. {
    7. ps->capacity == 4;
    8. }
    9. else
    10. {
    11. ps->capacity == ps->capacity * 2;
    12. }*/
    13. int newCapacity = ps->capacity == 0 ? 4 : 2*ps->capacity;
    14. SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
    15. if (tmp == NULL)
    16. {
    17. perror("realloc失败\n");
    18. return;
    19. }
    20. ps->a = tmp;
    21. ps->capacity = newCapacity;
    22. }
    23. }

    6.头部插入表和尾部插入表

    6.1尾部插入表 

     很简单,1.首先需要断言 ps 是否为空 NULL,因为为空指针,进行解引用的时候会变成野指针。所以要保证传进来的指针不为空。

    2.给一个上面的自动扩容表的函数,用来判断空间是否足够。

    3.如果足够 则将 ps size 这个空间的位置赋值为x 。(因为是数组,所以第一个数的下标是0.所以ps size 表示被占用的空间数量,比如说有三个空间被占用,ps size =3,而被占空间的下标是0 1 2,所以3这个空间是没数值的,直接赋值即可。)

    4.记得每次插入表给一个size ++;

    1. void SLPushBack(SL* ps,SLDataType x)
    2. {
    3.     assert(ps);
    4.     SLCheckCapacity(ps);
    5.     ps->a[ps->size] = x;
    6.     ps->size++;
    7. }

    6.2头部插入表 

    一样的先断言看看ps是否为空。

    1.与尾部插入不同的点就是,在头部插入,怎么在头部插入呢?

    可以用for循环从后往前遍历一遍,将每一个数值都往后移一个内存。

    2.然后再将 0 下标位置的内存赋值为 x。

    3.插入表一定要size++

    1. void SLPushFront(SL* ps, SLDataType x)
    2. {
    3. assert(ps);
    4. SLCheckCapacity(ps);
    5. for (int i = ps->size; i > 0; i--)
    6. {
    7. ps->a[i] = ps->a[i - 1];
    8. }
    9. ps->a[0] = x;
    10. ps->size++;
    11. }

    7.头部删除表和尾部删除表

    7.1判断表是否为空

    为什么要进行多一步判断表为空?

    可以想象如果一个表都为空了,那它还删除什么?那肯定报错啊

    所以在进行删除表的时候要进行判断表的有无。

    bool 表达式只有两个值 真就是为true 为1,假就是 false 为0.

    所以return的值要是一个表达式,用来判断是否为真或假。

    1. bool SLIsEmpty(SL* ps)
    2. {
    3. assert(ps);
    4. return ps->size == 0;
    5. }

    7.2尾部删除表

     超简单的删除尾表。

    1.一样的判断ps是否为空

    2.判断表是否为空

    3.对于一个开辟的表的内存,如果size--就说明有数据的空间-1. 下次在插入表数据的时候,直接使用这个空间,这样就是类似一个抽象的删除,因为下次添加数据还是用的这块内存,所以你不用给这块空间的数据清空也可以使用。

    可以想象一个二手汽车,前一个拥有人想把它卖了,这时候这车就是无主车,等下一个车主来了,直接把前一个车主的名字改为下一个车主的名字即可。

    1. void SLPopBack(SL* ps)
    2. {
    3. assert(ps);
    4. assert(!(SLIsEmpty(ps)));
    5. ps->size--;
    6. }

    (!SLIsEmpty( ps )) 这个表达式的意思就是 上面用bool函数判断 表中有没有数据,

    如果表中没有数据 就返回了一个1 ,用!来取反 所以这里就是 如果为1则断言程序终止。

    如果为0就说明 有数据 程序继续进行。

    7.3头部删除表

    1.首先还是判断ps是否为空,和表是否为空

    2.用for循环遍历把后一个的值赋予前一个。

    为什么是 i < ps size - 1.因为我们只要有数据的空间,如果是 ps size不减一 那就会把 有一个空着的数据传到 最后一个位置。这是不需要的,因为是删除表,最后一个位置要被放弃的。

    3.删除表 要 ps size --

    1. void SLPopFront(SL* ps)
    2. {
    3. assert(ps);
    4. assert(!(SLIsEmpty(ps)));
    5. for (int i = 0; i < ps->size - 1; i++)
    6. {
    7. ps->a[i] = ps->a[i + 1];
    8. }
    9. ps->size--;
    10. }

    8. 指定位置的插入和删除

    8.1指定位置的插入

    1.首先判断 ps 是否为空,判断 pos 是否超出表的范围。

    2.因为是插入表所以要判断空间是否足够

    3.用for循环 从后往前遍历 将前一个的数值赋值给后一个

    为什么是 i = ps size -1,因为 ps size 的值 是表示 有数据的空间个数,而对于空间的下标来说,ps size 这个数字的空间下表指向的反而是下一个无数据的空间。我们看第一组的转换

    ps a【i + 1】= ps a【i】如果把i 换了 就变成了ps a 【ps size】 = ps a 【ps size -1】。

    这样就很好理解了,把下一个空数据的内存赋值为 最后一个内存的值。

    pos -1,和上面类似,可以自己思考一下。(不懂可以私聊我,看到必回)

    4.最后在pos 那个位置插入数据 x

    5.插入表 ps size++

    1. void SLInsert(SL* ps, int pos, SLDataType x)
    2. {
    3. assert(ps);
    4. assert(pos >= 0 && pos <= ps->size);
    5. SLCheckCapacity(ps);
    6. for (int i = ps->size - 1; i > pos - 1; i--)
    7. {
    8. ps->a[i + 1] = ps->a[i];
    9. }
    10. ps->a[pos] = x;
    11. ps->size++;
    12. }

    8.2指定位置的删除 

    1.因为是删除首先判断 ps 是否为空 表是否为空,在判断 要查找的数字是否在表数据的范围内。

    2.这个删除和上面类似,就是要把前一个数据的值改为下一个数据的值即可,从pos的位置开始,因为要把pos位置的值给占领了。

    3.ps size--

    1. void SLErase(SL* ps, int pos)
    2. {
    3. assert(ps);
    4. assert(!SLIsEmpty(ps));
    5. assert(pos >= 0 && pos < ps->size);
    6. for (int i = pos; i < ps->size - 1; i++)
    7. {
    8. ps->a[i] = ps->a[i + 1];
    9. }
    10. ps->size--;
    11. }

    大家有什么不懂的可以私信问我,我也是刚学会!! 

  • 相关阅读:
    js将图片或者文件转成base64格式的两种方法
    Redis Cluster高可用集群原理
    python 断点续传下载
    Linux学习——进程间通信
    取代 C++,Google 强势开源 Carbon语言
    模块化编程
    Upload-labs(1-21关详细教程)【简单易懂】【万字教程】
    go-08-基本数据类型-整型
    微信h5 使用jssdk支付成功后,点击完成 页面关闭了,引出微信“点金计划“
    AWS Athena针对CSV文件切换SerDe Lib
  • 原文地址:https://blog.csdn.net/a1275174052/article/details/133890584