当前位置:   article > 正文

数据结构初阶——顺序表专题

数据结构初阶——顺序表专题

前言

本篇博客将为大家介绍数据结构中的顺序表的内容,它数据数据结构中的基础内容,希望所写内容能够为大家带来帮助,也希望大家多多支持;下面进入正文部分

1.数据结构的概念

数据结构::数据结构是计算机存储、组织数据的方式 

我们前面学过数组,数组就是一种数据结构,它是最基础的数据结构。

我们今天所要讨论的顺序表,其底层就是数组

2. 顺序表

顺序表是线性表的一种,什么是线性表呢?

线性表是相同特性的数据类型的集合,它在物理结构上不一定连续,在逻辑结构上是连续的;

而我们今天要说的顺序表是线性表的一种;

顺序表的特性:物理结构连续,逻辑结构连续。

顺序表可以分为两种:一种是静态顺序表,还有一种是动态顺序表;

2.1 静态顺序表

大家可以看到,上面就是静态顺序表的结构,包括一个定长数组、还有顺序表当前有效的数据个数。

2.2 动态顺序表

上面展示了动态顺序表的结构,大家发现,它与静态顺序表相比,多了一个成员,并且将定数组换成了指针的形式。

那么,请大家思考,静态顺序表和动态顺序表哪个更好呢?

其实这个问题显而易见,当然是动态顺序表更加实用。如果我们使用静态顺序表去存储一组不确定的信息,那么我们很难去精确地得知要存储多少,一旦我们定长数组的大小给的小了,那就会导致空间不够用,从而丢失信息;反之,如果我们害怕空间小了,一上来就给了一个很大的空间,那么这样也存在很大的问题,就是如果我们需要的空间远小于我们申请的空间,那将造成空间的浪费。

所以推荐大家使用动态顺序表,它可以动态增容,我们想要多少就申请多少,这样就不会担心空间不够或者空间大量浪费的问题

3. 顺序表的实现

关于顺序表的实现,我们需要三个文件,分别是头文件,顺序表的方法文件,测试文件;具体请大家仔细看下面的代码!!!

我将把整个代码拆解成各个小的部分来说明,最后会为大家展示完整代码,下面请大家认真阅读下面的内容。

首先,我们来将顺序表的结构写入头文件,大家看下面的代码

  1. typedef int SLDataType;
  2. typedef struct SeqList
  3. {
  4. SLDataType* arr;
  5. int size;
  6. int capacity;
  7. }SL;

 这里大家要注意,我将int定义成了SLDataType,这么做是为了我们后面方便修改代码,如果我们需要把int改为char,那么我们只需要把第一行的int改为char,这样后面的代码中的int就统一改成了char,实现了“一劳永逸”的效果。

3.1顺序表的初始化

首先我们需要在头文件中声明一下

void SLInit(SL* ps);

这里声明完之后,下面我们就需要在方法文件中写出具体的初始化的方法了

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

这里大家需要注意,我们必须得用指针去接受,也就是传址调用;当我们写完上面的代码后,下面我们需要在测试文件中测试一下,看看写的代码是否正确。

  1. void SLtest01()
  2. {
  3. SL sl;
  4. SLInit(&sl);
  5. }
  6. int main()
  7. {
  8. SLtest01();
  9. return 0;
  10. }

 这里就是测试代码,我们通过调试,可以得到下面的结果

大家可以发现,这里我们就初始化成功了,与我们在方法文件中的要求一样。 

3.2 顺序表的销毁

与初始化相同,我们需要在头文件中进行声明;

void SLDestroy(SL* ps);

声明完成后,我们在方法文件中写具体的销毁方法

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

这里大家发现,顺序表销毁的代码与初始化的代码比较类似,我们首先需要判断顺序表中的数组是否为空,如果有空间,那我们就需要进行销毁,这里用到free;然后我们需要将其置为空指针,并且将size和capacity都置为0。 

上面我们讨论完了初始化和销毁,下面来进行具体的增删查改的操作

3.3 顺序表的增容

在我们进行插入操作之前,我们需要注意顺序表的空间够不够,如果不够,我们需要先进行增容,下面为大家展示增容的具体方法;当然,我们可以将增容的过程包装成一个函数,那我们就需要在头文件中声明。

void SLCheckCapacity(SL* ps);

下面需要在方法文件中写出具体增容方法

  1. void SLCheckCapacity(SL* ps)
  2. {
  3. if (ps->size == ps->capacity)
  4. {
  5. int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
  6. SLDataType* tmp = (SLDataType * )realloc(ps->arr, newCapacity * sizeof(int));
  7. if (tmp == NULL)
  8. {
  9. perror("realloc fail");
  10. exit(1);
  11. }
  12. ps->arr = tmp;
  13. ps->capacity = newCapacity;
  14. }
  15. }

这里大家需要注意几点,首先,我们需要使用realloc来进行动态增容,并且判断空间是否足够,这里当size等于capacity时,就说明空间已经满了;

其次,我们要创建临时的指针变量,因为我们申请空间可能会失败,为了不让原始数据受到影响,我们需要创建临时的指针变量去保存我们申请的空间;同理,我们还创建了newCapacity来保护原始空间;

在申请空间的过程中,我们要进行强制类型转换,并将申请到的空间存放在临时的指针变量中;

中间加上一句判断,一旦申请失败,程序会直接报错并退出;

最后,我们将申请完的空间交给顺序表即可。

3.4 顺序表的尾插

首先,还是先进行声明

void SLPushBack(SL* ps, SLDataType x);

声明完后,我们来写具体的尾插方法

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

大家可以看到,尾插的代码很简单,这里大家需要注意,在我们尾插之前,需要进行assert断言。防止传入的是NULL,然后检查空间是否足够并进行增容操作;最后我们进行尾插。尾插完成后。我们需要让size自增。

3.5 顺序表的头插

第一步,先声明

void SLPushFront(SL* ps, SLDataType x);

 下面写出头插的具体代码

  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->arr[i] = ps->arr[i - 1];
  8. }
  9. ps->arr[0] = x;
  10. ps->size++;
  11. }

这里头插的过程就是将顺序表的每个数据都向后移动一位,并且是从后往前进行移动,最终会将首位空出来,这个时候我们将我们想要插入的数据插入到首位,这样就实现了头插的效果,最后,大家一定不要忘了让size自增,我们插入了数据,size就得跟着增大。

下面我们要进行测试;这里我们可以再写一个打印函数,将尾插和头插的结果全部打印出来;

我们在头文件中声明一下打印函数

void SLPrintf(SL s);

 然后我们写出具体代码

  1. void SLPrintf(SL s)
  2. {
  3. for (int i = 0; i < s.size; i++)
  4. {
  5. printf("%d ", s.arr[i]);
  6. }
  7. printf("\n");
  8. }

这里就是一个简单的循环,我们去打印每个元素;

下面,统一为大家展示尾插和头插的运行结果

大家可以看到,上面我们尾插和头插都实现了理想的效果。

3.6 顺序表的尾删

 前面说完了插入。下面来进行删除操作,咱们先来看尾删

第一步,还是先在头文件中声明

void SLPopBack(SL* ps);

 然后,写出具体的尾删代码

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

大家会发现。尾删代码很简洁,我们在进行删除操作前。需要保证顺序表不为空。也就是第二个断言;最后一行代码其实比较巧妙,我们直接让size自减,这样就相当于将尾部最后一个数据删除。 

下面来测试尾删代码

  

大家可以看到,尾删代码是成功的。

3.7 顺序表的头删 

和前面一样,还是先在头文件中声明

void SLPopFront(SL* ps);

 下面来写具体头删代码

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

大家可以发现,头删的代码和头插有些类似,只不过头删是要求我们让后一位的数据覆盖其前一位的数据,其实就是将顺序表的每个数据都相前移动一位,并且是从前往后进行移动;当头删操作完成后,我们需要让size自减,因为我们删除了一个数据。

最后,我们来测试一下头删代码,大家请看

这里我们成功地实现了头删的效果,证明我们的头删代码是正确的。这里还需要给大家提醒一下,在删除的时候,我们不能将顺序表删为空! 

3.8 在指定位置之前插入数据

首先,还是进行声明

void SLInsert(SL* ps, int pos, SLDataType x);

下面,我们来写出具体代码

  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; i > pos; i--)
  7. {
  8. ps->arr[i] = ps->arr[i - 1];
  9. }
  10. ps->arr[pos] = x;
  11. ps->size++;
  12. }

这里我们在进行指定位置插入时, 我们需要注意对pos的限制,首先“数组”的下标不能为负,其次pos也不能太大,不能超过size;前面说过,插入数据的时候,我们必须要判断顺序表的空间是否足够,所以我们在插入之前,需要先进行SLCheckCapacity;

再往下就与头删的代码很相似了,头删是将所有数据向后挪动一位;而现在我们需要将pos后的数据向后挪动一位,并且是从后往前进行挪动,这样我们就可以将pos这个位置空出来,将我们想要插入的数据进行插入,最后,我们要让size自增。因为我们增加了一个数据。

写完了方法,我们来测试一下;

大家可以看到,我们分别在不同位置实现了数据的插入,证明我们的代码是正确的.

3.9 删除指定位置的数据

上面说完了插入,现在来看删除数据

首先还是进行声明

void SLErase(SL* ps, int pos);

下面来实现删除代码

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

这里大家需要注意,在对pos限制时,与插入的代码有一点区别,就是pos不能等于size,因为size位置是没有数据的,所以也就不存在删除;然后具体删除本质上就是将pos后的数据向前移动一位,并且是从前往后进行移动最后,我们需要让size自减,因为我们删除了一个数据。

下面我们来进行测试

 大家可以发现,我删除了下标为1的数据,也就是第二个位置的数据;实现了想要的效果,证明我们的代码是正确的。

3.10 查找数据

 第一部还是先声明

int SLFind(SL* ps, SLDataType x);

下面来写查找代码

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

查找代码比较简单,只需要使用循环去遍历顺序表的数据,当找到时,返回这个数据的下标;如果找不到,那就返回-1(数组的下标不能为负)。

最后我们来进行测试

 

大家发现,我们可以找到对应的数据,说明代码没有问题。 

4. 顺序表的完整代码

OK,到这里,顺序表的内容就为大家介绍完毕了,下面为大家展示完整的代码,仅供参考

首先,来展示头文件SeqList.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. typedef int SLDataType;
  6. typedef struct SeqList
  7. {
  8. SLDataType* arr;
  9. int size;
  10. int capacity;
  11. }SL;
  12. void SLInit(SL* ps);
  13. void SLDestroy(SL* ps);
  14. void SLPrintf(SL s);
  15. void SLCheckCapacity(SL* ps);
  16. void SLPushBack(SL* ps, SLDataType x);
  17. void SLPushFront(SL* ps, SLDataType x);
  18. void SLPopBack(SL* ps);
  19. void SLPopFront(SL* ps);
  20. void SLInsert(SL* ps, int pos, SLDataType x);
  21. void SLErase(SL* ps, int pos);
  22. int SLFind(SL* ps, SLDataType x);

然后是顺序表的实现文件SeqList.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Seqlist.h"
  3. void SLInit(SL* ps)
  4. {
  5. ps->arr = NULL;
  6. ps->size = ps->capacity = 0;
  7. }
  8. void SLDestroy(SL* ps)
  9. {
  10. if (ps->arr)
  11. {
  12. free(ps->arr);
  13. }
  14. ps->arr = NULL;
  15. ps->size = ps->capacity = 0;
  16. }
  17. void SLCheckCapacity(SL* ps)
  18. {
  19. if (ps->size == ps->capacity)
  20. {
  21. int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
  22. SLDataType* tmp = (SLDataType * )realloc(ps->arr, newCapacity * sizeof(int));
  23. if (tmp == NULL)
  24. {
  25. perror("realloc fail");
  26. exit(1);
  27. }
  28. ps->arr = tmp;
  29. ps->capacity = newCapacity;
  30. }
  31. }
  32. void SLPushBack(SL* ps, SLDataType x)
  33. {
  34. assert(ps);
  35. SLCheckCapacity(ps);
  36. ps->arr[ps->size++] = x;
  37. }
  38. void SLPushFront(SL* ps, SLDataType x)
  39. {
  40. assert(ps);
  41. SLCheckCapacity(ps);
  42. for (int i = ps->size; i > 0; i--)
  43. {
  44. ps->arr[i] = ps->arr[i - 1];
  45. }
  46. ps->arr[0] = x;
  47. ps->size++;
  48. }
  49. void SLPrintf(SL s)
  50. {
  51. for (int i = 0; i < s.size; i++)
  52. {
  53. printf("%d ", s.arr[i]);
  54. }
  55. printf("\n");
  56. }
  57. void SLPopBack(SL* ps)
  58. {
  59. assert(ps);
  60. assert(ps->size);
  61. ps->size--;
  62. }
  63. void SLPopFront(SL* ps)
  64. {
  65. assert(ps);
  66. assert(ps->size);
  67. for (int i = 0; i < ps->size - 1; i++)
  68. {
  69. ps->arr[i] = ps->arr[i+1];
  70. }
  71. ps->size--;
  72. }
  73. void SLInsert(SL* ps, int pos, SLDataType x)
  74. {
  75. assert(ps);
  76. assert(pos >= 0 && pos <= ps->size);
  77. SLCheckCapacity(ps);
  78. for (int i = ps->size; i > pos; i--)
  79. {
  80. ps->arr[i] = ps->arr[i - 1];
  81. }
  82. ps->arr[pos] = x;
  83. ps->size++;
  84. }
  85. void SLErase(SL* ps, int pos)
  86. {
  87. assert(ps);
  88. assert(pos >= 0 && pos < ps->size);
  89. for (int i = pos; i < ps->size-1; i++)
  90. {
  91. ps->arr[i] = ps->arr[i + 1];
  92. }
  93. ps->size--;
  94. }
  95. int SLFind(SL* ps, SLDataType x)
  96. {
  97. assert(ps);
  98. for (int i = 0; i < ps->size; i++)
  99. {
  100. if (ps->arr[i] == x)
  101. {
  102. return i;
  103. }
  104. }
  105. return -1;
  106. }

 最后是我们的测试文件test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Seqlist.h"
  3. void SLtest01()
  4. {
  5. SL sl;
  6. SLInit(&sl);
  7. SLPushBack(&sl, 1);
  8. SLPushBack(&sl, 2);
  9. SLPushBack(&sl, 3);
  10. SLPushBack(&sl, 4);
  11. SLPrintf(sl);
  12. SLPushFront(&sl, 5);
  13. SLPushFront(&sl, 6);
  14. SLPrintf(sl);
  15. SLPopFront(&sl);
  16. SLPrintf(sl);
  17. SLPopFront(&sl);
  18. SLPrintf(sl);
  19. SLPopFront(&sl);
  20. SLPrintf(sl);
  21. SLPopBack(&sl);
  22. SLPrintf(sl);
  23. SLInsert(&sl, 0, 9);
  24. SLPrintf(sl);
  25. SLInsert(&sl, sl.size, 8);
  26. SLPrintf(sl);
  27. SLInsert(&sl, 1, 99);
  28. SLPrintf(sl);
  29. SLErase(&sl, 0);
  30. SLPrintf(sl);
  31. SLErase(&sl, 3);
  32. SLPrintf(sl);
  33. SLErase(&sl, 1);
  34. SLPrintf(sl);
  35. int find = SLFind(&sl, 4);
  36. if (find < 0)
  37. {
  38. printf("没找到\n");
  39. }
  40. else
  41. {
  42. printf("找到了,下标为%d\n", find);
  43. }
  44. SLDestroy(&sl);
  45. }
  46. int main()
  47. {
  48. SLtest01();
  49. return 0;
  50. }

5. 总结  

到这里,关于顺序表的内容就全部为大家介绍完了,希望大家认真去理解每一小部分的代码,注意其中的坑点,最终可以自己写出顺序表的所有内容;顺序表是数据结构中的基础内容,所以初学者一定要掌握好顺序表,为后面高阶数据的学习打好基础;最后,希望本篇博客的内容可以为大家带来帮助,如有错误,欢迎大家评论交流,谢谢!  

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

闽ICP备14008679号