当前位置:   article > 正文

顺序表的含义与顺序表数据操作代码详解!

顺序表的含义与顺序表数据操作代码详解!

一,顺序表的含义

        顺序表其实为线性表的一种,而线性表则代表着具有相同特性数据结构的集合。如果说顺序表是西瓜,链式表是葡萄,那么线性表则是水果,是一类含有相同特征的事物的总称。

        对于顺序表来说,它的底层逻辑其实就是数组,而我们知道,数组具有着物理结构上的连续和逻辑结构上的连续。

如图所示:

        所以对应到顺序表,则顺序表也同时具有以下特征:1,物理结构上为线性连续的。2,逻辑结构上也为线性连续的。

二,顺序表的分类

1,静态顺序表

        上文提到,顺序表就相当于数组,所以静态顺序表那么就相当于静态数组了。

        那我们又如何理解静态一词的含义呢?其实答案很简单,静态的含义就是数组的总大小无法发生改变。所以我们便可编写出以下代码来表示一个静态顺序表

  1. struct SeqList//创建一个静态顺序表的结构体
  2. {
  3. int arr[100];//创立一个定长数组
  4. int size;//size表示当前数组中有效元素的个数
  5. };

        但是当我们真正使用起来静态顺序表时,我们总会发现它的缺点是致命的。1,当有效元素较少时,会造成空间的浪费。2,当需要存储的数据较多时,静态顺序表可能无法完全保存。

        而这些缺点在实际应用中则会是致命的,所以为了满足我们对顺序表的使用需求,我们则会更偏向于使用另一种顺序表,叫做动态顺序表

 2,动态顺序表

        有了上文的静态顺序表做铺垫,那么理解动态顺序表一词就更简单了。

        何为动态?so easy,动态一词的含义其实就是数组的总大小可以发生改变。所以我们便可以编写出以下列代码来表示一个动态顺序表

  1. struct SeqList//创建一个动态顺序表的结构体
  2. {
  3. int* arr;//数组指针
  4. int size;//有效数据个数
  5. int capacity;//空间大小
  6. };

         后文中,我们将会基于动态顺序表来对其进行一系列的操作,以达到对动态顺序表中元素的增删查改工作!!

三,顺序表操作函数的实现

1,准备工作和注意事项

        首先,为了更好更有条理地实现顺序表操作函数,我们将生成三个文件。

        在SeqList.h文件中我们以如下方式定义结构体,因为顺序表中的数据可以为整型也可以为字符型,所以为了方便我们更改顺序表的数据类型,我们将使用typedef函数来命名顺序表的数据类型为SLdatatype

  1. typedef int SLdatatype;
  2. typedef struct SeqList//创建一个动态顺序表的结构体
  3. {
  4. SLdatatype* arr;//数组指针
  5. SLdatatype size;//有效数据个数
  6. SLdatatype capacity;//空间大小
  7. }SL;//将顺序表改名为SL

        其次,为了实现顺序表的动态增长效果,我们需要实现一个函数为SLcheck来判断顺序表的空间是否足够,若不够的话我们将使顺序表的空间大小(也就是capacity)呈2倍数增长。

  1. //顺序表的空间申请
  2. void SLcheck(SL* ps)
  3. {
  4. //查看空间是否足够,不够的话2倍增加空间
  5. if (ps->size == ps->capacity)//有效元素大小如果等于空间大小则代表着空间需要增长
  6. {
  7. int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//三目操作符
  8. //使用ralloc函数实现对空间的动态改变
  9. SLdatatype* temp = (SLdatatype*)realloc(ps->arr, 2 * sizeof(SLdatatype) * ps->size);
  10. //查看内存空间是否申请成功,不然就直接退出程序
  11. if (temp == NULL)
  12. {
  13. perror("realloc fail!");
  14. exit(1);
  15. }
  16. //空间申请成功
  17. ps->arr = temp;
  18. ps->capacity = newcapacity;
  19. }
  20. }

        最后,对于我们每实现一个顺序表操作函数,我们都必须在test.c中进行一次测试!以防在最后实现代码时错误过多而无法修改。

2,顺序表的初始化

        当我们拿到一个顺序表之后,我们需要对它进行初始化。而初始化则非常简单,我们只需要将顺序表中的数组指针初始化为NULL,size和capacity初始化为零就可以了。整个操作我们将于SeqList.c中进行。

  1. #include"SeqList.h"//包含头文件!!
  2. //顺序表的初始化函数
  3. void SLInit(SL* ps)//传址调用
  4. {
  5. ps->arr = NULL;
  6. ps->size = ps->capacity = 0;
  7. }

        当然,要记住头文件的包含传址调用。

         我们先应该在SeqList.h中声明这个函数,再使用test.c对这个函数进行测试!

        在test.c中,测试代码如下:

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. }
  7. int main()
  8. {
  9. test();
  10. return 0;
  11. }

        调试加监视代码走起来!

        顺序表初始化函数实现成功!

3,顺序表的销毁

        当我们使用完一个顺序表,我们需要对他进行销毁操作。我们只需要对数组指针所指向的内容free掉,然后将size和capacity置为零就可以了。

  1. //顺序表的销毁函数
  2. void SLDestroy(SL* ps)
  3. {
  4. if (ps->arr)//判断arr是否为空指针
  5. {
  6. ps->arr = NULL;
  7. free(ps->arr);//free函数释放空间
  8. }
  9. ps->size = ps->capacity = 0;//置零
  10. }

         然后在SeqList.h中声明后就可以在test.c中进行测试了。

        顺序表销毁函数实现成功!

4,顺序表的尾插

        现在准备进入顺序表中烧脑的部分了,但如果尾插听懂了,其他的操作函数对你来说那简直就是不在话下了。

        何为尾插?尾插就是在顺序表末尾插入一个数据。我们画图解释。

         如图所示,我们在顺序表中最后插入一个x,那么就相当于在下标为size的地方加入一个x,并且同时size也要往后移动一位。但是值得注意的是,如果顺序表空间不够,那我们又拿什么来存放插入的x呢?所以我们应当先使用SLcheck函数来判断空间是否足够,后再进行数据的插入

  1. //顺序表的尾插
  2. void SLPushBack(SL* ps, SLdatatype x)
  3. {
  4. assert(ps);//assert断言,判断ps是否为空
  5. SLcheck(ps);//检查顺序表空间是否足够
  6. ps->arr[ps->size] = x;//尾部插入x
  7. ps->size += 1;//size加1,表明有效元素个数加1
  8. }

        于SeqList.h中声明后,于test.c中测试。

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushBack(&s1, 2);
  8. SLPushBack(&s1, 3);
  9. SLPushBack(&s1, 4);
  10. SLDestroy(&s1);
  11. }
  12. int main()
  13. {
  14. test();
  15. return 0;
  16. }

        调试加监视,代码走起来,结果如下。

        顺序表尾插函数实现成功!

5,顺序表的头插

        头插头插,顾名思义其实就是在顺序表的头部插入一个数据x。我们画图解释。

        所以由图可知,每当我们想要在顺序表头部插入一个数据x的时候,我们需要先判断空间是否足够,再将顺序表的数据挨着挨着从后面开始依次向后移动一位,arr[1]=arr[0]则代表着最后一次元素的移动。最后再于arr[0]中插入x,同时size加1即可。所以代码如下。

  1. //顺序表的头插
  2. void SLPushFront(SL* ps, SLdatatype x)
  3. {
  4. assert(ps);//assert断言,ps是否为空
  5. SLcheck(ps);//判断空间是否足够
  6. for (int i = ps->size; i > 0; i--)
  7. {
  8. ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0];
  9. }
  10. ps->arr[0] = x;//顺序表头部插入x
  11. ps->size += 1;
  12. }

        在SeqList.h中声明后,于test.c中测试。

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushBack(&s1, 2);
  8. SLPushFront(&s1, 3);//头插
  9. SLPushFront(&s1, 4);//头插
  10. SLDestroy(&s1);
  11. }
  12. int main()
  13. {
  14. test();
  15. return 0;
  16. }

         调试加监视,代码走起来,结果如图。

        顺序表头插函数实现成功!

6,顺序表的尾删

        尾删函数则十分简单,因为根本不需要进行空间是否足够的判断和顺序表元素的移位操作,我们只需要判断ps和ps->arr是否为空,然后让size减1就可以了,所以画图如下:

        所以对于该函数,代码如下:

  1. //顺序表的尾部删除
  2. void SLDeleteBack(SL* ps)
  3. {
  4. assert(ps);//判断顺序表是否为空
  5. assert(ps->size);//判断顺序表是否有有效元素
  6. ps->size-=1;//顺序表有效元素减一
  7. }

        在SeqList.h中声明,后于test.c中进行测试。

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushFront(&s1, 4);
  8. SLDeleteBack(&s1);//顺序表的尾删
  9. SLDestroy(&s1);
  10. }
  11. int main()
  12. {
  13. test();
  14. return 0;
  15. }

        调试加监视,代码走起来!结果如图:

        顺序表尾删函数实现成功!

7,顺序表的头删

        顺序表的头删,就是删除顺序表的第一个元素。具体方式我们画图解释。

        所以由图可知,我们只需要从前往后依次将前面的元素向前移动一位,最后一次移位arr[size-2]=arr[size-1],再最后size减1。所以我们的顺序表头删函数如下:

  1. //顺序表的头部删除
  2. void SLDeleteFront(SL* ps)
  3. {
  4. assert(ps);
  5. assert(ps->arr);
  6. for (int i = 0; i < ps->size - 1; i++)
  7. {
  8. ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1]标志着最后一次移位;
  9. }
  10. ps->size -= 1;有效元素个数减1
  11. }

        在SeqList.h函数中声明,于test.c中测试

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushFront(&s1, 4);
  8. SLDeleteFront(&s1);//顺序表的头删
  9. SLDestroy(&s1);
  10. }
  11. int main()
  12. {
  13. test();
  14. return 0;
  15. }

        调试加监视,代码走起来,结果如下:

        顺序表头删函数实现成功!

8,顺序表指定位置插入数据

        该方法其实也就包括了顺序表的头插和尾插,该函数可以做到顺序表指定位置插入数据。还是老样子,咱们画图解释!

        由图知,该函数有着一个新的参数pos(表示想要插入的位置的下标),当然一定不能忘记pos有大小限制,在写函数的时候我们应该对pos进行限制。其次,每当我们插入一个数的时候,我们应当首先判断空间是否足够,然后再将顺序表从最后到pos的元素一个一个向右移动一位,直到最后一次移位为arr[pos+1]=arr[pos]。最后再插入x(别忘了size要加1),这样,一个顺序表操作的新函数就被我们刨析地十分清楚了。那么我们的函数如下:

  1. //顺序表中指定位置之前插入数据
  2. void SLInsert(SL* ps, int pos, SLdatatype x)
  3. {
  4. assert(ps);
  5. assert(ps->arr);
  6. assert(pos >= 0 && pos < ps->size);//对pos的值进行限定!
  7. SLcheck(ps);//判断空间是否足够
  8. for (int i = ps->size; i > pos; i--)
  9. {
  10. ps->arr[i] = ps->arr[i - 1];//arr[pos+1]=arr[pos];
  11. }
  12. ps->arr[pos] = x;//插入x
  13. ps->size += 1;//有效元素个数加1
  14. }

        老样子,先声明再测试:

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushBack(&s1, 2);
  8. SLPushBack(&s1, 3);
  9. SLPushBack(&s1, 4);
  10. SLInsert(&s1, 1, 6);//在下标为1的地方插入数据x=6
  11. SLDestroy(&s1);
  12. }
  13. int main()
  14. {
  15. test();
  16. return 0;
  17. }

         调试加监视,代码咱们走起来!结果如图:

        顺序表指定位置s插入数据函数实现成功!

9,顺序表指定位置删除数据 

        该函数的作用是删除指定位置的数据,当然,它的功能肯定比头删和尾删更强大。话不多说,咱们直接上图!

        通过看图后,我们可以很清楚地发现,当我们想要删除pos位置的数据时,其实我们可以直接使用arr[pos+1]位置的数据将它覆盖掉,并以此类推,将顺序表中pos之后的元素从前往后一个一个向前移动一位,最后一次移动为arr[size-2]=arr[size-1]。最后的最后一定不要忘掉size减1。那么函数代码如下:

  1. //顺序表中指定位置删除数据
  2. void SLErase(SL* ps, int pos)
  3. {
  4. assert(ps);
  5. assert(ps->arr);
  6. assert(pos >= 0 && pos < ps->size);
  7. for (int i = pos; i < ps->size - 1; i++)
  8. {
  9. ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1];
  10. }
  11. ps->size -= 1;//顺序表有效数据减少1个
  12. }

        咱们先声明,再于test.c中测试:

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushBack(&s1, 2);
  8. SLPushBack(&s1, 3);
  9. SLErase(&s1, 1);//删除顺序表中下标为1的数据
  10. SLDestroy(&s1);
  11. }
  12. int main()
  13. {
  14. test();
  15. return 0;
  16. }

      调试加监视,代码走起来,结果如图所示:

        顺序表指定位置删除数据函数实现成功!

10,顺序表的查找

        该函数的作用是从顺序表中找到你想要的那个数据,并且返回数据的下标,如果没找到,那么就返回一个无效值(其中无效值我们可以用-1来表示,因为数组的下标肯定不会为负数)

        这么简单的函数我们就不用画图展示了,使用for循环便可以搞定我们想要的操作,直接上代码!

  1. //顺序表中指定数据的查找
  2. int SLFind(SL* ps, SLdatatype x)
  3. {
  4. assert(ps);
  5. assert(ps->arr);
  6. for (int i = 0; i < ps->size; i++)
  7. {
  8. if (ps->arr[i] == x)
  9. {
  10. //找到了
  11. return i;
  12. }
  13. }
  14. //没有找到
  15. return -1;
  16. }

        老规矩,先声明再测试:

  1. #include"SeqList.h"
  2. void test()
  3. {
  4. SL s1;
  5. SLInit(&s1);
  6. SLPushBack(&s1, 1);
  7. SLPushBack(&s1, 2);
  8. SLPushBack(&s1, 3);
  9. int find1 = SLFind(&s1, 2);//寻找顺序表中数据为2的元素的下标
  10. int find2 = SLfind(&s1, 5);//寻找顺序表中数据为5的元素的下标
  11. SLDestroy(&s1);
  12. }
  13. int main()
  14. {
  15. test();
  16. return 0;
  17. }

         调试加监视,代码走起来看结果!

        find1的值为1,说明下标为1,find2的值为-1,说明该数据不存在。

        那么顺序表查找函数实现成功!

四,结语

        六千四百多个字,再加图片加代码,手指头酸死了,希望我的blog讲解的足够详细,能对你有所帮助,有错误也欢迎指正,希望您可以给我一套三连,您的支持就是对我莫大的鼓励!!!

       

         

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

闽ICP备14008679号