当前位置:   article > 正文

【数据结构】详解顺序表

顺序表

目录

一、线性表

二、顺序表

1.顺序表的概念

2.顺序表的分类

(1)静态顺序表

(2)动态顺序表

3.顺序表的接口实现

三、顺序表的应用

四、总结


一、线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,.常见的线性表:顺序表、链表、栈、队列、字符串.. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

本篇文章主要是介绍线性表的其中一种——顺序表

二、顺序表

1.顺序表的概念

顺序表是用一段物理地址连续的内存空间依次存储数据的线性结构。一般采用数组存储。在数组上完成顺序表的增删查改。更准确的说,线性表是逻辑层面的概念,顺序表是物理层面的概念,顺序表就是用数组实现的线性表。

2.顺序表的分类

(1)静态顺序表

用定长数组实现。

  1. #define MAX 100
  2. typedef int SLDataType;
  3. typedef struct SeqList
  4. {
  5. SLDataType a[MAX];//定长数组
  6. int size;
  7. }SL;

(2)动态顺序表

用动态开辟的数组实现。

  1. #define InitMax 5
  2. typedef int SLDataType;
  3. typedef struct SeqList
  4. {
  5. SLDataType* a;//指向动态开辟的内存
  6. int size;
  7. int capacity;//容量
  8. }SL;

3.顺序表的接口实现

  设计函数实现对顺序表的初始化、增 (头插、尾插、指定下标)删(头删、尾删、指定下标)查改。(以动态顺序表为例)

头文件SeqList.h:主要内容包括头文件的包含,结构体定义和接口函数的声明。

  1. //SeqList.h
  2. #pragma once
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <assert.h>
  7. typedef int SLDataType;//typedef用于基本数据类型取别名,便于顺序表中数组元素类型的多样化
  8. typedef struct SeqList
  9. {
  10. SLDataType* a;//指向动态内存开辟的内存
  11. int size;
  12. int capacity;//容量
  13. }SeqList;
  14. // 基本增删查改接口
  15. // 顺序表初始化
  16. void SeqListInit(SeqList* psl);
  17. // 顺序表打印
  18. void SeqListPrint(SeqList* psl);
  19. // 检查空间,如果满了,进行增容
  20. void CheckCapacity(SeqList* psl);
  21. // 顺序表尾插
  22. void SeqListPushBack(SeqList* psl, SLDataType x);
  23. // 顺序表尾删
  24. void SeqListPopBack(SeqList* psl);
  25. // 顺序表头插
  26. void SeqListPushFront(SeqList* psl, SLDataType x);
  27. // 顺序表头删
  28. void SeqListPopFront(SeqList* psl);
  29. // 顺序表查找
  30. int SeqListFind(SeqList* psl, SLDataType x);
  31. // 顺序表在pos位置插入x
  32. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
  33. // 顺序表删除pos位置的值
  34. void SeqListErase(SeqList* psl, size_t pos);
  35. // 顺序表销毁
  36. void SeqListDestory(SeqList* psl);

 源文件SeqList.c:主要内容为函数接口的实现。

1.SeqListInit()函数

函数功能:实现顺序表的初始化。将结构体成员变量置为0(指针置为NULL)。

2.SeqListPrint()函数

函数功能:实现顺序表内容的打印。用for循环打印,一共打印psl->size个;打印的数据是结构体成员变量数组的元素。

3.CheckCapacity()函数(头插、尾插函数都需要先调用该函数,再进行插入操作)

函数功能:通过判断psl->size和psl->capacity的大小关系来检查数组是否已经存满数据。如果两个数大小相等,将容量扩大到原来的2倍(如果容量大小为0则置为4)。将容量扩大后用realloc函数扩大结构体数组的空间,操作结束后记得判断realloc的返回值,为空则扩容失败,函数直接结束。

4.SeqListPushFront()函数

函数功能:头插一个数到结构体成员数组中。先调用CheckCapacity()函数检查容量是否足够(不足够会扩容),然后从最后一个元素psl->a[psl->size - 1]开始,每个元素依次向后挪一位,挪完后将第一个元素赋值为要插入的值。最后psl->size加一。

5.SeqListPopFront()函数

函数功能:删除结构体成员数组的第一个元素。先断言一下实现顺序表的结构体指针,避免指针为空。然后从第二个元素开始依次向前挪一位,挪完后psl->size减一。

6.SeqListPushBack()函数

函数功能:尾插一个数到结构体成员数组中。先检查容量。然后将数组的下标为psl->size的元素赋值为要插入的值,然后psl->size加一。(注意:数组元素个数为psl->size,原本的数组的最后一个元素下标为psl->size-1)

7.SeqListPopBack()函数

函数功能:删除结构体成员变量数组的最后一个元素。先断言,避免结构体指针为空。然后直接psl->size减一。

8.SeqListFind()函数

函数功能:查找结构体数组中是否有与x相等的元素,如有,返回下标,如无,返回-1。遍历数组,判断是否有与x相等的元素。

9.SeqListInsert()函数

函数功能:将数x插入结构体成员数组的指定下标pos处。先断言结构体指针,确保指针不为空。然后断言pos的范围,pos>=0 并且pos <= psl->size。然后从最后一个元素开始,一直到下标为pos的元素,依次向后挪一个,然后将x赋值给psl->a[pos]。psl->size加一。

10.SeqListErase()函数

函数功能:将结构体成员数组下标为pos处的元素删除。先断言结构体指针,避免为空指针。断言pos的范围,pos>= 0 并且pos< psl->size。然后从下标为pos+1的元素开始,一直到最后一个元素,依次向前挪一个,然后psl->size减一。

11.SeqListDestroy()函数

函数功能:用free函数释放动态申请的内存空间,将指针置空,将psl->size和psl->capacity置为0。

  1. //SeqList.c
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include "SeqList.h"
  4. // 基本增删查改接口
  5. // 顺序表初始化
  6. void SeqListInit(SeqList* psl)
  7. {
  8. psl->a = NULL;
  9. psl->size = 0;
  10. psl->capacity = 0;
  11. }
  12. // 顺序表打印
  13. void SeqListPrint(SeqList* psl)
  14. {
  15. for (int i = 0;i < psl->size;i++)
  16. {
  17. printf("%d ", psl->a[i]);
  18. }
  19. printf("\n");
  20. }
  21. // 检查空间,如果满了,进行增容
  22. void CheckCapacity(SeqList* psl)
  23. {
  24. if (psl->size == psl->capacity)//容量满了,需要扩容
  25. {
  26. int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;//如果原容量==0,则置为4,不为0,则扩大为原来的两倍
  27. //动态申请内存空间,大小为新容量*单个元素的大小,,因为可能要多次扩容,所以必须使用realloc。
  28. SeqList* tmp = (SeqList*)realloc(psl->a,sizeof(SLDataType) * newcapacity);
  29. if (tmp != NULL)//动态申请成功
  30. {
  31. psl->a = tmp;
  32. psl->capacity = newcapacity;
  33. }
  34. else//动态申请失败
  35. {
  36. printf("realloc failed\n");
  37. return;
  38. }
  39. }
  40. }
  41. // 顺序表头插
  42. void SeqListPushFront(SeqList* psl, SLDataType x)
  43. {
  44. CheckCapacity(psl);//检查容量
  45. for (int i = psl->size - 1;i >= 0;i--)
  46. {
  47. psl->a[i + 1] = psl->a[i];//从最后一位依次向后挪一位
  48. }
  49. psl->a[0] = x;
  50. psl->size++;//插入一个顺序表元素后,其psl->size的值等于下一个元素的下标
  51. }
  52. // 顺序表头删
  53. void SeqListPopFront(SeqList* psl)
  54. {
  55. assert(psl);//断言
  56. for (int i = 0;i < psl->size - 1;i++)
  57. {
  58. psl->a[i] = psl->a[i + 1];//从第二个元素开始,依次向前挪一位
  59. }
  60. psl->size--;
  61. }
  62. // 顺序表尾插
  63. void SeqListPushBack(SeqList* psl, SLDataType x)
  64. {
  65. CheckCapacity(psl);//检查容量
  66. psl->a[psl->size] = x;
  67. psl->size++;//插入一个顺序表元素后,其psl->size的值等于下一个元素的下标
  68. }
  69. // 顺序表尾删
  70. void SeqListPopBack(SeqList* psl)
  71. {
  72. psl->a[psl->size - 1] = 0;
  73. psl->size--;
  74. }
  75. // 顺序表查找
  76. int SeqListFind(SeqList* psl, SLDataType x)
  77. {
  78. for (int i = 0;i < psl->size;i++)
  79. {
  80. if (psl->a[i] == x)
  81. return i;//返回对应元素第一次出现的下标
  82. }
  83. return -1;//没找到,返回-1
  84. }
  85. // 顺序表在pos位置插入x
  86. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
  87. {
  88. for (int i = psl->size - 1;i >= pos;i--)
  89. {
  90. psl->a[i + 1] = psl->a[i];//从最后一个元素开始,到下标为pos的元素为止,依次向后挪一位
  91. }
  92. psl->a[pos] = x;
  93. psl->size++;
  94. }
  95. // 顺序表删除pos位置的值
  96. void SeqListErase(SeqList* psl, size_t pos)
  97. {
  98. for (int i = pos;i < psl->size - 1;i++)
  99. {
  100. psl->a[i] = psl->a[i + 1];//从下标为pos+1的元素开始,依次向前挪一位
  101. }
  102. psl->size--;
  103. }
  104. // 顺序表销毁
  105. void SeqListDestory(SeqList* psl)
  106. {
  107. free(psl->a);//释放动态申请的内存空间
  108. psl->a = NULL;//置空指针
  109. psl->size = 0;
  110. psl->capacity = 0;
  111. }
  1. //Test.c
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include "SeqList.h"
  4. int main()
  5. {
  6. SeqList sl;
  7. SeqListInit(&sl);
  8. //测试头插函数 成功
  9. SeqListPushFront(&sl, 1);
  10. SeqListPushFront(&sl, 2);
  11. SeqListPushFront(&sl, 3);
  12. SeqListPushFront(&sl, 4);
  13. SeqListPushFront(&sl, 5);
  14. SeqListPushFront(&sl, 6);
  15. //SeqListPrint(&sl);//6 5 4 3 2 1
  16. //测试头删函数 成功
  17. SeqListPopFront(&sl);
  18. SeqListPopFront(&sl);
  19. SeqListPopFront(&sl);
  20. SeqListPopFront(&sl);
  21. //SeqListPrint(&sl);//2 1
  22. //测试尾插函数 成功
  23. SeqListPushBack(&sl, 2);
  24. SeqListPushBack(&sl, 3);
  25. SeqListPushBack(&sl, 4);
  26. SeqListPushBack(&sl, 5);
  27. SeqListPushBack(&sl, 6);
  28. SeqListPushBack(&sl, 7);
  29. //SeqListPrint(&sl);//2 1 2 3 4 5 6 7
  30. //测试尾删函数 成功
  31. SeqListPopBack(&sl);
  32. SeqListPopBack(&sl);
  33. SeqListPopBack(&sl);
  34. SeqListPopBack(&sl);
  35. SeqListPopBack(&sl);
  36. //SeqListPrint(&sl);//2 1 2
  37. //测试查找函数 成功
  38. //printf("%d\n", SeqListFind(&sl, 1));//1 找到了,下标为1
  39. //printf("%d\n", SeqListFind(&sl, 2));//0 找到了,下标为0
  40. //printf("%d\n", SeqListFind(&sl, 3));//-1 没找到
  41. //测试在pos位置插入x的函数 成功
  42. SeqListInsert(&sl, 2, 1);//2 1 1 2
  43. SeqListInsert(&sl, 2, 2);//2 1 2 1 2
  44. //SeqListPrint(&sl);//2 1 2 1 2
  45. //测试删除pos位置的值的函数 成功
  46. SeqListErase(&sl, 0);//1 2 1 2
  47. SeqListErase(&sl, 2);//1 2 2
  48. //SeqListPrint(&sl);//1 2 2
  49. SeqListDestory(&sl);
  50. //SeqListPrint(&sl);
  51. return 0;
  52. }

三、顺序表的应用

1.通讯录的实现

2.原地移除元素:https://leetcode-cn.com/problems/remove-element/

3.合并两个有序数组:https://leetcode-cn.com/problems/merge-sorted-array/

(上述两题的解题思路都可以使用双指针,后续博客会总结一些与双指针解法有关的题)

四、总结

1.书写函数较多的代码时,比较好的一个习惯是,写一个测一个,可以有效避免写完之后代码报错但由于代码量过大无从下手的问题。

2.动态申请的空间一定要记得正确释放,避免造成内存泄漏。

谢谢各位的支持!

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

闽ICP备14008679号