当前位置:   article > 正文

数据结构 - 顺序表基本操作_顺序表的基本操作

顺序表的基本操作

目录

前言:

接口实现

顺序表基本操作

顺序表初始化(SeqListInit)

检查空间进行增容(SeqListCheckCapacity)

顺序表打印(SeqListPrint)

 销毁(SeqListDestroy)

1、尾插(SeqListPushBack)

2、尾删(SeqListPopBack)

3、头插(SeqListPushFront)

4、头删(SeqListPopFront)

5、查找位置(SeqListFind)

6、在指定的下标位置插入(SeqListInsert)

7、删除指定位置的数据(SeqListErase) 

 代码封装优化

   完整代码:

SeqList.h:头文件以及函数申明

SeqList.c:接口实现

OJ练习:


前言:

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

 顺序表

顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组
上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素
  1. #define N 7
  2. typedef int SLDataType
  3. typedef struct SeqList {
  4. SLDataType array[N]; //定长数组
  5. size_t size; //有效数据的个数
  6. } SeqList;

2. 动态顺序表:使用动态开辟的数组存储。

  1. typedef int SLDataType
  2. typedef struct SeqList {
  3. SLDataType* array; //指向动态开辟的数组
  4. size_t size; //有效数据的个数
  5. size_t capacity; //容量空间的大小
  6. } SeqList;

接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致 N 定大了,空间开多了浪 费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实 现动态顺序表。
为了方便后续修改数据类型,我们可以使用 typedef 定义一个新的数据类型,这里我们把它取名为  SLDataType
为了让定义的结构体使用时更方便,我们同样可以使用 typedef 将其定义为  SeqList(此时 SeqList = struct SeqList,后续在使用时可以更加方便)。
  1. typedef int SLDataType;
  2. // 顺序表的动态存储
  3. typedef struct SeqList
  4. {
  5. SLDataType* array; // 指向动态开辟的数组
  6. size_t size ; // 有效数据个数
  7. size_t capicity ; // 容量空间的大小
  8. }SeqList;

 顺序表的所以基本操作:

  1. // 基本增删查改接口
  2. // 顺序表初始化
  3. void SeqListInit(SeqList* psl, size_t capacity);
  4. // 检查空间,如果满了,进行增容
  5. void SeqListCheckCapacity(SeqList* psl);
  6. // 顺序表尾插
  7. void SeqListPushBack(SeqList* psl, SLDataType x);
  8. // 顺序表尾删
  9. void SeqListPopBack(SeqList* psl);
  10. // 顺序表头插
  11. void SeqListPushFront(SeqList* psl, SLDataType x);
  12. // 顺序表头删
  13. void SeqListPopFront(SeqList* psl);
  14. // 顺序表查找
  15. int SeqListFind(SeqList* psl, SLDataType x);
  16. // 顺序表在pos位置插入x
  17. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
  18. // 顺序表删除pos位置的值
  19. void SeqListErase(SeqList* psl, size_t pos);
  20. // 顺序表销毁
  21. void SeqListDestory(SeqList* psl);
  22. // 顺序表打印
  23. void SeqListPrint(SeqList* psl);

顺序表基本操作

前情提要:assert()函数

#include "assert.h" 
void assert( int expression );

assert 的作用是现计算表达式 expression ,如果其值为假(即为0)或者为NULL,那么将直接报错。(断言(assert)的用法 | 菜鸟教程 (runoob.com)

下文中 assert(psl);用于防止错误并方便Debug,因为assert会精准的报出错误位置;

顺序表初始化(SeqListInit)

  1. void SeqListInit(SeqList* psl)
  2. {
  3. assert(psl);
  4. psl->a = NULL;
  5. psl->size = 0; //有效数据个数
  6. psl->capacity = 0; //数组实际能存数据的空间容量是多大
  7. }

检查空间进行增容(SeqListCheckCapacity)

size_t等价于unsigned int

  1. void SeqListCheckCapacity(SeqList* psl)
  2. {
  3. assert(psl);
  4. // 如果满了,我们要扩容
  5. if (psl->size == psl->capacity)
  6. {
  7. size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
  8. SLDataType* tmp = realloc(psl->a, sizeof(SLDataType) * newCapacity);
  9. if (tmp == NULL)
  10. {
  11. printf("realloc fail\n");//扩容失败,直接退出
  12. exit(-1);
  13. }
  14. else
  15. {
  16. psl->a = tmp;
  17. psl->capacity = newCapacity;
  18. }
  19. }
  20. }
  • 这里巧妙地使用三目操作符:int new_capacity = psl->capacity == 0 ? 4 : psl->capacity*2 , 如果 capacity 为 0 (第一次使用大小是0),就把4赋值给它;如果不为0,就把 capacity 的值翻一倍(x2)。
  • 你想一次扩容多少就扩容多少,乘2只是一个比较折中的扩容容量方案。 

顺序表打印(SeqListPrint)

  1. void SeqListPrint(SeqList* psl)
  2. {
  3. assert(psl);
  4. for (int i = 0; i < psl->size; ++i)
  5. {
  6. printf("%d ", psl->a[i]);
  7. }
  8. printf("\n");
  9. }
因为是单纯的打印,所以只需要把顺序表传过去就行。

 销毁(SeqListDestroy)

因为是动态开辟的,所以如果空间不用我们就需要销毁掉。如果不销毁会存在内存泄漏的风险,所以与之对应的我们写一个销毁的接口函数。

  1. void SeqListDestroy(SeqList* psl)
  2. {
  3. assert(psl);
  4. free(psl->a);
  5. psl->a = NULL;
  6. psl->capacity = psl->size = 0;
  7. }

1、尾插(SeqListPushBack)

  1. void SeqListPushBack(SeqList* psl, SLDataType x)
  2. {
  3. assert(psl);
  4. SeqListCheckCapacity(psl);//查看是否要扩容
  5. psl->a[psl->size] = x;
  6. psl->size++;
  7. }

2、尾删(SeqListPopBack)

  1. void SeqListPopBack(SeqList* psl)
  2. {
  3. assert(psl);
  4. //psl->a[psl->size - 1] = 0;
  5. if (psl->size > 0)
  6. {
  7. psl->size--;
  8. }
  9. }

对于尾删,其实只要让最后一个元素访问不到就行,即size--即可。

3、头插(SeqListPushFront)

  1. void SeqListPushFront(SeqList* psl, SLDataType x)
  2. {
  3. assert(psl);
  4. SeqListCheckCapacity(psl);
  5. // 挪动数据,腾出头部空间
  6. int end = psl->size - 1;
  7. while (end >= 0)
  8. {
  9. psl->a[end + 1] = psl->a[end];
  10. --end;
  11. }
  12. psl->a[0] = x;
  13. psl->size++;
  14. }

4、头删(SeqListPopFront)

  1. void SeqListPopFront(SeqList* psl)
  2. {
  3. assert(psl);
  4. // 挪动数据覆盖删除
  5. if (psl->size > 0)
  6. {
  7. int begin = 1;
  8. while (begin < psl->size)
  9. {
  10. psl->a[begin - 1] = psl->a[begin];
  11. ++begin;
  12. }
  13. --psl->size; //相当于让指针指向第二个元素
  14. }
  15. }

5、查找位置(SeqListFind)

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

 6、在指定的下标位置插入(SeqListInsert)

  1. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
  2. {
  3. // 暴力检查
  4. assert(psl);
  5. // 温和检查
  6. if (pos > psl->size)
  7. {
  8. printf("pos 越界:%d\n", pos);
  9. return;
  10. }
  11. SeqListCheckCapacity(psl);
  12. size_t end = psl->size;
  13. while (end > pos)
  14. {
  15. psl->a[end] = psl->a[end - 1];
  16. --end;
  17. }
  18. psl->a[pos] = x;
  19. psl->size++;
  20. }

 7、删除指定位置的数据(SeqListErase) 

  1. void SeqListErase(SeqList* psl, size_t pos)
  2. {
  3. assert(psl);
  4. assert(pos < psl->size);
  5. size_t begin = pos + 1;
  6. while (begin < psl->size)
  7. {
  8. psl->a[begin - 1] = psl->a[begin];
  9. ++begin;
  10. }
  11. psl->size--;
  12. }

此时,我们发现代码有许多共同点;比如头插、尾插不就是SeqListInsert在下标为0和下标为size()时插入吗?同理:头删、尾删也可以用SeqListErase代替

 代码封装优化

  1. void SeqListPushBack(SeqList* psl, SLDataType x)
  2. {
  3. assert(psl);
  4. SeqListInsert(psl, psl->size, x);
  5. }
  6. void SeqListPopBack(SeqList* psl)
  7. {
  8. assert(psl);
  9. SeqListErase(psl, psl->size - 1);
  10. }
  11. void SeqListPushFront(SeqList* psl, SLDataType x)
  12. {
  13. assert(psl);
  14. SeqListInsert(psl, 0, x);
  15. }
  16. void SeqListPopFront(SeqList* psl)
  17. {
  18. assert(psl);
  19. SeqListErase(psl, 0);
  20. }

完整代码:

SeqList.h:头文件以及函数申明

  1. #pragma once//为了避免同一个头文件被包含多次我们可以使用 #pragma once
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <assert.h>
  5. // 要求:存储的数据从0开始,依次连续存储
  6. // 静态的顺序表
  7. // 问题:开小了,不够用。开大了,存在浪费。
  8. //#define N 10000
  9. //struct SeqList
  10. //{
  11. // int a[N];
  12. // int size; // 记录存储了多少个数据
  13. //};
  14. typedef int SLDataType;
  15. // 动态的顺序表
  16. typedef struct SeqList
  17. {
  18. SLDataType* a;
  19. int size; // 存储数据个数
  20. int capacity; // 存储空间大小
  21. }SL, SeqList;
  22. void SeqListPrint(SeqList* psl);
  23. //void SLInit(SeqList* psl);
  24. void SeqListInit(SeqList* psl);
  25. void SeqListDestroy(SeqList* psl);
  26. void SeqListCheckCapacity(SeqList* psl);
  27. // 时间复杂度是O(1)
  28. void SeqListPushBack(SeqList* psl, SLDataType x);
  29. void SeqListPopBack(SeqList* psl);
  30. // 时间复杂度是O(N)
  31. void SeqListPushFront(SeqList* psl, SLDataType x);
  32. void SeqListPopFront(SeqList* psl);
  33. // 在pos位置插入x
  34. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
  35. // 删除pos位置的数据
  36. void SeqListErase(SeqList* psl, size_t pos);
  37. // 顺序表查找
  38. int SeqListFind(SeqList* psl, SLDataType x);

SeqList.c:接口实现

  1. #include "SeqList.h"
  2. void SeqListPrint(SeqList* psl)
  3. {
  4. assert(psl);
  5. for (int i = 0; i < psl->size; ++i)
  6. {
  7. printf("%d ", psl->a[i]);
  8. }
  9. printf("\n");
  10. }
  11. void SeqListInit(SeqList* psl)
  12. {
  13. assert(psl);
  14. psl->a = NULL;
  15. psl->size = 0;
  16. psl->capacity = 0;
  17. }
  18. void SeqListDestroy(SeqList* psl)
  19. {
  20. assert(psl);
  21. free(psl->a);
  22. psl->a = NULL;
  23. psl->capacity = psl->size = 0;
  24. }
  25. void SeqListCheckCapacity(SeqList* psl)
  26. {
  27. assert(psl);
  28. // 如果满了,我们要扩容
  29. if (psl->size == psl->capacity)
  30. {
  31. size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
  32. SLDataType* tmp = realloc(psl->a, sizeof(SLDataType) * newCapacity);
  33. if (tmp == NULL)
  34. {
  35. printf("realloc fail\n");
  36. exit(-1);
  37. }
  38. else
  39. {
  40. psl->a = tmp;
  41. psl->capacity = newCapacity;
  42. }
  43. }
  44. }
  45. void SeqListPushBack(SeqList* psl, SLDataType x)
  46. {
  47. assert(psl);
  48. SeqListInsert(psl, psl->size, x);
  49. }
  50. void SeqListPopBack(SeqList* psl)
  51. {
  52. assert(psl);
  53. SeqListErase(psl, psl->size - 1);
  54. }
  55. void SeqListPushFront(SeqList* psl, SLDataType x)
  56. {
  57. assert(psl);
  58. SeqListInsert(psl, 0, x);
  59. }
  60. void SeqListPopFront(SeqList* psl)
  61. {
  62. assert(psl);
  63. SeqListErase(psl, 0);
  64. }
  65. // 在pos位置插入x
  66. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
  67. {
  68. // 暴力检查
  69. assert(psl);
  70. // 温和检查
  71. if (pos > psl->size)
  72. {
  73. printf("pos 越界:%d\n", pos);
  74. return;
  75. //exit(-1);
  76. }
  77. // 暴力检查
  78. //assert(pos <= psl->size);
  79. SeqListCheckCapacity(psl);
  80. size_t end = psl->size;
  81. while (end > pos)
  82. {
  83. psl->a[end] = psl->a[end - 1];
  84. --end;
  85. }
  86. psl->a[pos] = x;
  87. psl->size++;
  88. }
  89. // 删除pos位置的数据
  90. void SeqListErase(SeqList* psl, size_t pos)
  91. {
  92. assert(psl);
  93. assert(pos < psl->size);
  94. size_t begin = pos + 1;
  95. while (begin < psl->size)
  96. {
  97. psl->a[begin - 1] = psl->a[begin];
  98. ++begin;
  99. }
  100. psl->size--;
  101. }
  102. int SeqListFind(SeqList* psl, SLDataType x)
  103. {
  104. assert(psl);
  105. for (int i = 0; i < psl->size; ++i)
  106. {
  107. if (psl->a[i] == x)
  108. {
  109. return i;
  110. }
  111. }
  112. return -1;
  113. }

笔记篇:参考资料->比特科技

OJ练习:

链表的结构图:

203. 移除链表元素

 

 思路:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* removeElements(struct ListNode* head, int val){
  9. struct ListNode*prve=NULL;
  10. struct ListNode*cur=head;
  11. while(cur){ //遍历整个数组
  12. if(cur->val!=val){ //如果不是,则共同前进一步
  13. prve=cur;
  14. cur=cur->next;
  15. }
  16. else{ //如果是,则如图所示
  17. struct ListNode*next=cur->next;
  18. if(prve==NULL){ //头删情况,因为头删时没有prve->next
  19. free(cur);
  20. head=next;
  21. cur=head;
  22. }
  23. else{
  24. free(cur);
  25. prve->next=next;
  26. cur=next;
  27. }
  28. }
  29. }
  30. return head;
  31. }

206. 反转链表

思路:直接把链表的指向反转即可

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode() : val(0), next(nullptr) {}
  7. * ListNode(int x) : val(x), next(nullptr) {}
  8. * ListNode(int x, ListNode *next) : val(x), next(next) {}
  9. * };
  10. */
  11. class Solution {
  12. public:
  13. ListNode* reverseList(ListNode* head) {
  14. struct ListNode*newHead=NULL;
  15. struct ListNode*cur=head;
  16. while(cur){
  17. struct ListNode*next=cur->next;
  18. cur->next=newHead; //将“箭头”反转
  19. newHead=cur;
  20. cur=next;
  21. }
  22. return newHead;
  23. }
  24. };
  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode() : val(0), next(nullptr) {}
  7. * ListNode(int x) : val(x), next(nullptr) {}
  8. * ListNode(int x, ListNode *next) : val(x), next(next) {}
  9. * };
  10. */
  11. class Solution {
  12. public:
  13. ListNode* reverseList(ListNode* head) {
  14. if(head==NULL)
  15. return NULL;
  16. struct ListNode*n1,*n2,*n3;
  17. n1=NULL;
  18. n2=head;
  19. n3=n2->next;
  20. while(n2){
  21. n2->next=n1;
  22. n1=n2;
  23. n2=n3;
  24. if(n3) //作图可知,最后一个n3=NULL,无n3->next
  25. n3=n3->next;
  26. }
  27. return n1;
  28. }
  29. };

C++写法

方法一:迭代

  1. class Solution {
  2. public:
  3. ListNode* reverseList(ListNode* head) {
  4. ListNode* p=nullptr;
  5. ListNode* curr=head;
  6. while(curr)
  7. {
  8. ListNode* next=curr->next;
  9. curr->next=p;
  10. p=curr;
  11. curr=next;
  12. }
  13. return p;
  14. }
  15. };

方法二:递归

  • 使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 retret .
  • 此后,每次函数在返回的过程中,让当前结点的下一个结点的 nextnext 指针指向当前节点。
  • 同时让当前结点的 nextnext 指针指向 NULLNULL ,从而实现从链表尾部开始的局部反转
  • 当递归函数全部出栈后,链表反转完成。

 

 

  1. class Solution {
  2. public:
  3. ListNode* reverseList(ListNode* head) {
  4. if(!head||!head->next)
  5. return head;
  6. ListNode* ret=reverseList(head->next);
  7. head->next->next=head;
  8. head->next=NULL;
  9. return ret;
  10. }
  11. };

26. 删除有序数组中的重复项 - 力扣(LeetCode)

经典快慢指针!! 

  1. class Solution {
  2. public:
  3. int removeDuplicates(vector<int>& nums) {
  4. int n=nums.size();
  5. int slow=1,fast=1;
  6. while(fast<n)
  7. {
  8. if(nums[fast]!=nums[fast-1])
  9. {
  10. nums[slow++]=nums[fast];
  11. }
  12. fast++;
  13. }
  14. return slow;
  15. }
  16. };

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

闽ICP备14008679号