当前位置:   article > 正文

【数据结构】顺序表 超详细!

顺序表

目录

一.顺序表定义

1 、顺序表的概念及结构

1.1线性表

2、顺序表分类

2.1 静态顺序表

2.2 动态顺序表

二、动态顺序表的实现 ( 重要!)

1.准备工作及其注意事项

1.1 先创建三个文件

1.2 注意事项:帮助高效记忆和理解

2.顺序表的基本功能接口

2.0 创建一个 顺序表

2.1 顺序表的初始化

2.2 顺序表的销毁

2.3 顺序表的打印

3.顺序表的扩容检查接口

4.顺序表的增加功能接口

4.1 尾插接口

4.2 头插接口

4.3 指定位置插入接口

5.顺序表的删除功能接口

5.1 尾删接口

5.2头删接口

5.3指定位置删除接口

6.顺序表的查找实现接口

三.总代码

SeqList.h

SepList.c

test.c



一.顺序表定义

1 、顺序表的概念及结构

1.1线性表

线性表(linearlist)是n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使  用的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串...  

线性表在逻辑上是线性结构,也就说是连续的一条直线。

但是在物理结构上并不一定是连续的,  线性表在物理上存储时,通常以数组和链式结构的形式存储。  简单来说顺序表是线性表的一种,而且逻辑和物理都是线性结构

2、顺序表分类

顺序表和数组的区别

◦ 顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口
2.1 静态顺序表
概念:使用定长数组存储元素
静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费
  1.  静态顺序表(这里拿里面存储的整形举例)
  2.  typedef int SLDataType;
  3.  #define N 100
  4.  typedef struct SeqList
  5.  {
  6.      SLDatatype arr[N];
  7.      int size;//size是有效数据的个数
  8.  }SL;
  9.  
  10.  解释一下为什么要把存储的类型变为SLDataType
  11.  这么做是为了方便以后修改 使用 不同 的类型
  12.  还有把数组存储的数据容量N 便于修改
2.2 动态顺序表

前言:

动态顺序表的结构更加灵活, 可以对结构进行 增删查改,所以我们下面的接口都是通过动态顺序表实现的

二、动态顺序表的实现(重要!)

1.准备工作及其注意事项

1.1 先创建三个文件

 解释这三个文件的作用
 1、头文件SeqList.h  是来声明接口函数,定义顺序表,将几个公共用到的库函数集合起来
 2、源文件SeqList.c  是用来具体实现接口
 3、源文件test.c  用于接口的测试工作 ,即具体的使用场景

1.2 注意事项:帮助高效记忆和理解
 1.不管写任意一种接口函数,我们在处理之前都要进行断言,避免穿过来的是NULL指针  assert(ps->arr) ;
 2.在进行扩容操作 的时候别忘记了 realloc要将新空间的地址和容量 更新给 自己的数组 和 容量变量(在下文)
 3.进行插入操作之后  要把size++;  在进行删除操作的时候要进行size--;(很重要的一点)
 4、插入操作 和 删除操作 用到循环 数据整体后移 或 前移 一定要注意 边界!!!

2.顺序表的基本功能接口

2.0 创建一个 顺序表
  1. //动态顺序表
  2. typedef int SLDataType;// 定义一个 类型名字,方便快速修改 类型
  3. typedef struct SeqList
  4. {
  5. SLDataType* arr;
  6. int capacity; // 记录动态顺序表 申请空间的大小
  7. int size;// 记录 顺序表 当前有效的数据个数
  8. }SL;
  9. // 给 结构体命名,翻遍方便使用:直接写在下面也可以
  10. // typedef struct SeqList SL;
2.1 顺序表的初始化
  1. void SLInit(SL* ps)
  2. {
  3. assert(ps);
  4. ps->arr = NULL;
  5. ps->capacity = ps->size = 0;
  6. }
2.2 顺序表的销毁
  1. void SLDestroy(SL* ps)
  2. {
  3. assert(ps);
  4. free(ps->arr);
  5. ps->arr = NULL;
  6. ps->capacity = ps->size = 0;
  7. }
2.3 顺序表的打印
  1. // 打印顺序表:这里可以 传值 也可 传址 ,
  2. // 因为不需要对数据进行修改(可以传值),但是 为了保持接口一致性(其他接口都是传址),这里直接使用 传址
  3. void SLPrint(SL* ps)
  4. {
  5. for (int i = 0; i < ps->size; ++i) printf("%d ", ps->arr[i]);
  6. printf("\n");
  7. }

3.顺序表的扩容检查接口

  1. // 公共的扩容判断
  2. void SLCheckCapacity(SL* ps)
  3. {
  4. //当容量capacity 大小 == 标记点size 时,说明 已经满了:需要 扩容
  5. if (ps->size == ps->capacity)
  6. {
  7. // 开辟空间时注意,当capacity初始化为 0 时,直接相乘的结果还是 0 !!
  8. // 所以要先判断一下, 并且需要做 申请空间是否成功的判断,
  9. // 还不能直接将申请的空间赋值给 目标数组,先给一个 临时变量
  10. int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
  11. // 每次 两倍空间扩容(tip:这里推荐2倍的倍数扩容,可以自行查找为什么倍数扩容 较好!)
  12. SLDataType* ptmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
  13. if (ptmp == NULL)
  14. {
  15. perror("realloc fail!");
  16. exit(1);
  17. }
  18. // 扩容成功
  19. ps->arr = ptmp;
  20. ps->capacity = newCapacity;
  21. }
  22. }

4.顺序表的增加功能接口

4.1 尾插接口
  1. // (1)尾插法:有三种情况:1) 头部非空,尾部还有空间;2)全部为空;3)全部满了
  2. void SLPushBack(SL* ps, SLDataType x)
  3. {
  4. // 先判断指针是否非空
  5. assert(ps);
  6. // 空间不够,扩容 : 3)
  7. SLCheckCapacity(ps);
  8. // 空间足够,直接插入:1 ) ; 2 )
  9. // 在 size 这个位置插入数据: size 是标记第一个空位置
  10. ps->arr[ps->size++] = x;
  11. // ps->size++; // 标记点size 需要即使更新:size++ 也可以合并在上面
  12. }
4.2 头插接口
  1. // 头插法
  2. void SLPushFront(SL* ps, SLDataType x)
  3. {
  4. assert(ps);
  5. // 空间不够,扩容 : 3)
  6. SLCheckCapacity(ps);
  7. // 将数据向后挪动:注意边界问题!!!
  8. for (int i = ps->size; i > 0; --i) ps->arr[i] = ps->arr[i - 1];
  9. ps->arr[0] = x; // 将插入数据 放在头部
  10. ps->size++; // 别忘 了 更新 size
  11. }
4.3 指定位置插入接口
  1. // 指定位置之前插入数据
  2. void SLInsert(SL* ps, int pos, SLDataType x)
  3. {
  4. assert(ps);
  5. // 注意 : 可以指定位置插入 说明 指定的位置pos 可能不合理,可能越界, 必须要断言判断一下
  6. assert(0 <= pos && pos < ps->size); // 注意 不能等于 size!!!!
  7. // 判断是否有足够的空间
  8. SLCheckCapacity(ps);
  9. // 在 pos 位置中 插入 数据 x :就要将 pos 位置 起的 后面数据向后挪动
  10. for (int i = ps->size; i >= pos + 1; --i) // 让 pos 这个位置 空出来
  11. {
  12. ps->arr[i] = ps->arr[i - 1];
  13. }
  14. ps->arr[pos] = x;
  15. ps->size++;
  16. }

5.顺序表的删除功能接口

5.1 尾删接口
  1. void SLPopBack(SL* ps)
  2. {
  3. // 删除前 检查:顺序表非空才能执行删除,同时 size 不能为 0
  4. assert(ps);
  5. assert(ps->size);// 顺序表不为空
  6. ps->size--;
  7. // 直接 size-- 将 size 位置的数值 丢出 我们的有效数据空间,那个位置不属于有效空间了,则类似达到删除的效果
  8. }
5.2头删接口
  1. void SLPopFront(SL* ps)
  2. {
  3. // 删除前 检查:顺序表非空才能执行删除,同时 size 不能为 0
  4. assert(ps);
  5. assert(ps->size);
  6. // 将 后面的 数据 向前移动: 注意边界!!
  7. for (int i = 1; i < ps->size; ++i) ps->arr[i - 1] = ps->arr[i];
  8. ps->size--;
  9. }
5.3指定位置删除接口
  1. void SLErase(SL* ps, int pos)
  2. {
  3. assert(ps);
  4. // 注意 : 可以指定位置插入 说明 指定的位置可能不合理,可能越界, 必须要断言判断一下
  5. assert(0 <= pos && pos < ps->size); // 注意 不能等于 size
  6. for (int i = pos + 1; i < ps->size; ++i)
  7. {
  8. ps->arr[i - 1] = ps->arr[i];
  9. }
  10. ps->size--;
  11. }

6.顺序表的查找实现接口

  1. int SLFind(SL* ps, int x)
  2. {
  3. for (int i = 0;i < ps->size;++i)
  4. {
  5. if (ps->arr[i] == x)
  6. {
  7. return i;
  8. }
  9. }
  10. return -1;
  11. }

三.总代码

SeqList.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. typedef int SLDataType;
  6. typedef struct SeqList
  7. {
  8. SLDataType* arr;
  9. int Capacity;
  10. int size;
  11. }SL;
  12. // 一、初始化
  13. void SLInit(SL* ps);
  14. // 二、 尾插法
  15. void SLPushBack(SL* ps, SLDataType x);
  16. // 二、 头插法
  17. void SLPushFront(SL* ps, SLDataType x);
  18. // 三、尾删法
  19. void SLPopBack(SL* ps);
  20. // 四、头删法
  21. void SLPopFront(SL* ps);
  22. // 五、指定位置删除
  23. void SLErase(SL* ps, int pos);
  24. // 六、指定位置插入
  25. void SLInsert(SL* ps, int pos, SLDataType x);
  26. //顺序表的扩容检查(扩容的原则,以及扩容的方法)
  27. void SLCheckCapacity(SL* ps);
  28. // 打印函数
  29. void SLPrint(SL* ps);

SepList.c

  1. #include"SeqList.h"
  2. void SLInit(SL* ps)
  3. {
  4. ps->arr = NULL;
  5. ps->Capacity = ps->size = 0;
  6. }
  7. // 开辟空间函数
  8. void SLCheckCapacity(SL* ps)
  9. {
  10. if (ps->size == ps->Capacity)
  11. {
  12. int NewCapacity = ps->Capacity == 0 ? 4 : 2 * (ps->Capacity);
  13. SLDataType* ptmp = realloc(ps->arr, NewCapacity * sizeof(SLDataType));
  14. if (ptmp == NULL)
  15. {
  16. perror("realloc fail !");
  17. exit(1);
  18. }
  19. ps->arr = ptmp;
  20. ps->Capacity = NewCapacity;
  21. }
  22. }
  23. // 二、 尾插法
  24. void SLPushBack(SL* ps, SLDataType x)
  25. {
  26. // 先判断指针是否非空
  27. assert(ps); // 别忘了
  28. // 分两大类情况:空间足够 和 空间不够
  29. // (1) 空间不够:空间扩展
  30. SLCheckCapacity(ps);
  31. // 开始尾插
  32. ps->arr[ps->size++] = x;
  33. }
  34. // 二、 头插法
  35. void SLPushFront(SL* ps, SLDataType x)
  36. {
  37. assert(ps); // 别忘了
  38. SLCheckCapacity(ps);
  39. for (int i = ps->size; i > 0; --i)
  40. {
  41. ps->arr[i] = ps->arr[i - 1];
  42. }
  43. ps->arr[0] = x;
  44. ps->size++; // 别忘记了
  45. }
  46. // 三、尾删法
  47. void SLPopBack(SL* ps)
  48. {
  49. assert(ps);
  50. assert(ps->size);
  51. ps->size--;
  52. }
  53. // 四、头删法
  54. void SLPopFront(SL* ps)
  55. {
  56. assert(ps);
  57. assert(ps->size);
  58. for (int i = 0; i < ps->size - 1; ++i)
  59. {
  60. ps->arr[i] = ps->arr[i + 1];
  61. }
  62. ps->size--;
  63. }
  64. // 五、指定位置删除
  65. void SLErase(SL* ps, int pos)
  66. {
  67. assert(ps);
  68. assert(0 <= pos && pos < ps->size);
  69. for (int i = pos; i < ps->size - 1; ++i)
  70. {
  71. ps->arr[i] = ps->arr[i + 1];
  72. }
  73. ps->size--;
  74. }
  75. // 六、指定位置插入
  76. void SLInsert(SL* ps, int pos, SLDataType x)
  77. {
  78. assert(ps); // 别忘了
  79. assert(0 <= pos && pos < ps->size);
  80. SLCheckCapacity(ps);
  81. for (int i = ps->size; i >= pos + 1; --i)
  82. {
  83. ps->arr[i] = ps->arr[i - 1];
  84. }
  85. ps->arr[pos] = x;
  86. ps->size++; // 别忘记了
  87. }
  88. // 打印函数
  89. void SLPrint(SL* ps)
  90. {
  91. for (int i = 0; i < ps->size; ++i)
  92. {
  93. printf("%d ", ps->arr[i]);
  94. }
  95. printf("\n");
  96. }

test.c

  1. #include"SeqList.h"
  2. void slTest01()
  3. {
  4. SL sl; // 创建一个结构体变量 sl
  5. SLInit(&sl); // 这里需要 传地址:才能真正被初始化
  6. //测试尾插
  7. SLPushBack(&sl, 1);
  8. SLPushBack(&sl, 2);
  9. SLPushBack(&sl, 3);
  10. SLPushBack(&sl, 4); // ctrl + d == ctrl + c + v
  11. printf("测试尾插: ");
  12. SLPrint(&sl);
  13. // 测试头插
  14. SLPushFront(&sl, 10);
  15. printf("测试头插: ");
  16. SLPrint(&sl);
  17. // 测试 尾删
  18. SLPopBack(&sl);
  19. printf("测试尾删: ");
  20. SLPrint(&sl);
  21. // 测试 头删
  22. SLPopFront(&sl);
  23. printf("测试头删: ");
  24. SLPrint(&sl);
  25. // 测试 指定位置插入
  26. SLInsert(&sl, 1, 15);
  27. printf("指定插入: ");
  28. SLPrint(&sl);
  29. SLErase(&sl, 1);
  30. printf("指定删除: ");
  31. SLPrint(&sl);
  32. }
  33. int main()
  34. {
  35. slTest01(); // 第一个测试函数
  36. return 0;
  37. }

四、顺序表的问题及思考

问题:

1. 中间/头部的插⼊删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不⼩的消耗。

3. 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?

有没有 这样一个 数据结构

1、中间 和 头尾的 数据插入 不用 挪动 其他数据,而是一步到位

2、不需要直接扩充连续的空间(用不完容易浪费,像上面第 3 点所说)

3、不会造成空间浪费

这样的强大的数据结构 即为  【 链表 】

可以 点击跳转 讲解 单链表 的实现文章  ---->:【数据结构】单链表的实现


完。

若上述文章有什么错误,欢迎各位大佬及时指出,我们共同进步!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/926376
推荐阅读
相关标签
  

闽ICP备14008679号