顺序表知识点

简介: 顺序表知识点

本章重点

(1)线性表(2)顺序表


一 线性表

 线性表 ( linear list ) 是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二 顺序表

顺序表的英文是sequence list

2.1 概念和结构

 顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。(顺序表就是数组,只是增加了一些要求)

顺序表一般可以分为:(1)静态顺序表:使用定长数组存储元素 。

1. //静态
2. #define N 100
3. //顺序表要求存储的数据从0开始,连续依次存储
4. struct SeqList
5. {
6.  int a[N];
7.  int size;//记录了存储多少个数据
8. };//当前结构体,是一个静态的顺序表,不是特别的实用
9. //出现的问题:会出现浪费空间或者空间不够用的情况

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

1. //动态的顺序表,更加的实用
2. typedef int SLDateType;
3. 
4. struct SeqList
5. {
6.  SLDateType* a;
7.  int size;//存储的个数
8.  int capacity;//存储空间大小
9. };

2.2 接口实现

实现动态顺序表

SeqList.h

1. #pragma once
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <assert.h>
5. 
6. 
7. //静态
8. //#define N 100
9. //顺序表要求存储的数据从0开始,连续依次存储
10. //struct SeqList
11. //{
12. //  int a[N];
13. //  int size;//记录了存储多少个数据
14. //};//当前结构体,是一个静态的顺序表,不是特别的实用
15. //出现的问题:会出现浪费空间或者空间不够用的情况
16. 
17. 
18. //动态的顺序表,更加的实用
19. typedef int SLDateType;
20. 
21. typedef struct SeqList
22. {
23.   SLDateType* a;
24.   int size;//存储的个数
25.   int capacity;//存储空间大小
26. }SeqList;
27. 
28. 
29. void SeqListCheckCapacity(SeqList* psl);//检查是否需要扩容
30. 
31. 
32. 
33. void SeqListPrint(SeqList* psl);//打印
34. 
35. void SeqListInit(SeqList* psl);//初始化
36. void SeqListDestroy(SeqList* psl);//销毁
37. 
38. void SeqListPushBack(SeqList* psl, SLDateType x);//尾插 不要命名为中文拼音
39. void SeqListPopBack(SeqList* psl);//尾删
40. void SeqListPushFront(SeqList* psl, SLDateType x);//头插
41. void SeqListPopFront(SeqList* psl);//头删
42. void SeqListInsert(SeqList* psl, size_t pos, SLDateType x);//下标为pos的位置插入
43. void SeqListErase(SeqList* psl, size_t pos); //下标为pos的位置删除
44. int SeqListFind(SeqList* psl, SLDateType x);//查找

SeqList.c

1. #define _CRT_SECURE_NO_WARNINGS 1
2. #include "SeqList.h"
3. 
4. //代码重复的内容
5. //扩容
6. void SeqListCheckCapacity(SeqList* psl)
7. {
8.  assert(psl);
9.  if (psl->size == psl->capacity)
10.   {
11.     size_t newCapacity = psl->capacity == 0 ? 4 : (psl->capacity) * 2;//因为,我们初始化结构体的时候,容量初始化的值为0,当我们想要扩容的时候,同时容量也要增加
12.     SLDateType* tmp = realloc(psl->a, sizeof(SLDateType) * newCapacity);//当容量的数值增加后,用realloc进行扩容
13.     if (tmp == NULL)
14.     {
15.       printf("realloc fail\n");
16.       exit(-1);
17.     }
18.     else
19.     {
20.       psl->a = tmp;
21.       psl->capacity = newCapacity;
22.     }
23.   }
24. }
25. 
26. 
27. 
28. //主代码
29. //打印数组
30. void SeqListPrint(SeqList* psl)
31. {
32.   assert(psl);
33.   for (int i = 0; i < psl->size; ++i)
34.   {
35.     printf("%d ", psl->a[i]);
36.   }
37.   printf("\n");
38. }
39. //初始化
40. void SeqListInit(SeqList* psl)
41. {
42.   assert(psl);
43.   psl->a = NULL;
44.   psl->size = 0;
45.   psl->capacity = 0;
46. }
47. //销毁
48. void SeqListDestroy(SeqList* psl)
49. {
50.   assert(psl);
51.   free(psl->a);
52.   psl->a = NULL;
53.   psl->size = 0;
54.   psl->capacity = 0;
55. }
56. //尾插,如果空间足够,直接添加
57. void SeqListPushBack(SeqList* psl, SLDateType x)
58. {
59.   //本来尾插的代码(1)
60.   //assert(psl);
61.   //SeqListCheckCapacity(psl);
62.   //psl->a[psl->size] = x;//把x这个数据放进去
63.   //psl->size++;
64.   //因为插入函数写过,就可以直接用插入函数(2)
65.   SeqListInsert(psl, psl->size, x);
66. }
67. //尾删
68. void SeqListPopBack(SeqList* psl)
69. {
70.   assert(psl);
71.   if (psl->size > 0)
72.   {
73.     psl->size--;
74.   }
75. }
76. //头插
77. void SeqListPushFront(SeqList* psl, SLDateType x)
78. {
79.   assert(psl);
80.   //注意,从前面插入,又要求连续,需要将整体向后平移一下。(正确的思路应该是从后向前,依次向后移一位)
81.   //如果,从前向后的话进行移位,会导致数据被覆盖
82.   SeqListCheckCapacity(psl);//扩容
83.   //开始插入数据
84.   int end = psl->size - 1;//这里不能写size_t,因为写成size_t,当size为0的时候,会导致end是一个非常大的数字,
85.   while (end >= 0)
86.   {
87.     psl->a[end + 1] = psl->a[end];
88.     --end;
89.   }
90.   psl->a[0] = x;
91.   psl->size++;
92. }
93. //头删
94. void SeqListPopFront(SeqList* psl)
95. {
96.   assert(psl);
97.   //要考虑size=0的情况,是不可以再次进行头删
98.   if (psl->size > 0)
99.   {
100.    int begin = 0;
101.    while (begin <= psl->size - 1)
102.    {
103.      psl->a[begin] = psl->a[begin + 1];
104.      ++begin;
105.    }
106.    --psl->size;
107.  }
108. }
109. // 下标为pos的位置插入
110. void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
111. {
112.  assert(psl);//暴力
113.  //下标为size这个位置也是可以的,相当于尾插//size_t不会小于0的
114.  if (pos > psl->size)
115.  {
116.    printf("pose越界:%d\n", pos);
117.    return;
118.  }
119.  SeqListCheckCapacity(psl);
120.  //这个是错误的代码,
121.  //size_t end = psl->size - 1;// end是size_t类型的,size为0的时候,在进行减一操作后,会变成一个很大的数字,所以就错了,
122.  // 改成int类型的话在while进行比较的时候,会发生整形提升,有符号的和无符号进行比较,会被提升为无符号的,所以还是while循环还是可以进去,达不到我们想要的效果,我们想要的效果是,进不去这个循环
123.  //while (end >= pos)
124.  //{
125.  //  psl->a[end + 1] = psl->a[end];
126.  //  end--;
127.  //}
128.  //psl->a[pos] = x;
129.  //++psl->size;
130.  //正确的代码,不让end涉及-1,
131.  size_t end = psl->size;
132.  while (end > pos)
133.  {
134.    psl->a[end] = psl->a[end - 1];
135.    end--;
136.  }
137.  psl->a[pos] = x;
138.  ++psl->size;
139. }
140. //下标为pos的位置删除
141. void SeqListErase(SeqList* psl, size_t pos)
142. {
143.  assert(psl);
144.  assert(pos < psl->size);
145.  size_t begin = pos + 1;
146.  while (begin < psl->size )
147.  {
148.    psl->a[begin - 1] = psl->a[begin];
149.    begin++;
150.  }
151.  psl->size--;
152. }
153. 
154. //查找
155. int SeqListFind(SeqList* psl, SLDateType x)
156. {
157.  assert(psl);
158.  for (int i = 0; i < psl->size; ++i)
159.  {
160.    if (psl->a[i] == x)
161.    {
162.      return i;
163.    }
164.  }
165.  return -1;
166. }

易错点:

1. void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
2. {
3.  assert(psl);//暴力
4.  //下标为size这个位置也是可以的,相当于尾插//size_t不会小于0的
5.  if (pos > psl->size)
6.  {
7.    printf("pose越界:%d\n", pos);
8.    return;
9.  }
10.   SeqListCheckCapacity(psl);
11.   //这个是错误的代码,
12.   //size_t end = psl->size - 1;// end是size_t类型的,size为0的时候,在进行减一操作后,会变成一个很大的数字,所以就错了,
13.   // 改成int类型的话在while进行比较的时候,会发生整形提升,有符号的和无符号进行比较,会被提升为无符号的,所以还是while循环还是可以进去,达不到我们想要的效果,我们想要的效果是,进不去这个循环
14.   //while (end >= pos)
15.   //{
16.   //  psl->a[end + 1] = psl->a[end];
17.   //  end--;
18.   //}
19.   //psl->a[pos] = x;
20.   //++psl->size;
21.   //正确的代码(1),不让end涉及-1,
22.   size_t end = psl->size;//如果,end是int类型的,那么在while进行比较的时候,会发生整形提升,有符号的和无符号
23.   while (end > pos)
24.   {
25.     psl->a[end] = psl->a[end - 1];
26.     end--;
27.   }
28.   psl->a[pos] = x;
29.   ++psl->size;
30. }

上述错误代码中的解决办法除了上面的代码还有(2)int end = ps->size - 1;while (end > (int)pose);这样写,就不会发生整形提升.

知识点:(1)结构体传参,形参是实参的临时拷贝,形参的改变,并不影响实参。同时结构体传参,如果传递的是形参,实参就无法改变。同时形参是实参的一份临时拷贝,所以,会浪费大量内存,所以结构体传参,应该传递的是结构体的地址。

(2)越界不一定能查出来,越界是抽查,有时候能检查出来,有时候检查不出来

(3)realloc()两种扩容方式,一种原地扩容(效率高),一种异地扩容。

建议:(1) 写项目的时候,建议菜单最后写,方便测试。

(2)代码进行检查的时候,做到边写边测,进行边测,进行测试的时候,要多考虑特殊情况,数据初始的时候 ,数据的两级考虑。

2.3 数组相关面试题

1.移除数组(力扣)

原地移除数组中所有的元素 val ,要求时间复杂度为 O(N) ,空间复杂度为 O(1) 。

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

思路一: 遍历整个数组,查找到每一个val,碰到val就把他覆盖掉(数组后面的元素向前挪动数据)【时间复杂度为O(N*N),空间复杂度为O(1)】

思路二:把不是val的值拷贝到新的数组里(双指针(数组))【时间复杂度O(N),空间复杂度O(N)】【不符合题意】

思路三:双指针,但是不开辟新的数组

思路三代码:

1. int removeElement(int* nums, int numsSize, int val){
2. int src = 0;
3. int dst = 0;
4. while (src < numsSize)
5.     {
6. if (nums[src] != val)
7.         {
8.             nums[dst] = nums[src];
9.             src++;
10.             dst++;
11.         }
12. else
13.         {
14.             src++;
15.         }
16.     }
17. return dst;
18. }

2. 删除排序数组中的重复项。

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

思路: (双指针)src从1开始,每次与前一个下表的内容进行比较,如果相同,dst不加一,src加一,如果不相同, src-1的值赋值给dst,并且src和dst分别加一(注意,src要与前一个下表的内容进行比较,所以src从1开始),最后的时候,把 最后一个下标的值也赋值给dst。

1. int removeDuplicates(int* nums, int numsSize){
2. int src = 1;
3. int dst = 0;
4. while (src < numsSize)
5.     {
6. if (nums[src - 1] == nums[src])
7.         {
8.             src++;
9.         }
10. else
11.         {
12.             nums[dst] = nums[src - 1];
13.             src++;
14.             dst++;
15.         }
16.     }
17.     nums[dst] = nums[numsSize - 1];
18. return dst + 1;
19. }

3. 合并两个有序数组

 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

代码展示:

1. void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
2. int i = m - 1;
3. int j = n - 1;
4. int dst = m + n -1;
5. while (i >= 0 && j >= 0)
6.     {
7. if (nums1[i] > nums2[j])
8.         {
9.             nums1[dst--] = nums1[i--];
10.         }
11. else
12.         {
13.             nums1[dst--] = nums2[j--];
14.         }
15.     }
16. while (j >= 0)
17.     {
18.         nums1[dst--] = nums2[j--];
19.     }
20. }

思路一:(这个需要开额外数组)(归并)两个有序【无序的就把它变成有序的】数组,依次比较,每次把小的放到归并数组里,当有一个数组遍历完之后,就把另一个数组里剩下的元素放到归并数组里(使得归并数组里的元素也是有序的)【注意:非递减序列就说明数组是有序的】【不符合题意,因为用了一个新的数组】

思路二:【双指针】,因为num1是包括所有空间的,所以,可以倒着放【大的先放】,【这两个数组也倒序着进行比较】【这样就和思路一差不多一致】【符合题意】

2.4 顺序表的问题

优点:连续的空间,方便下标随机访问。

问题:

1. 中间 / 头部的插入删除,时间复杂度为 O(N),效率比较低

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

3. 增容一般是呈 2 倍的增长,势必会有一定的空间浪费。例如当前容量为 100 ,满了以后增容到 200 ,我们再继续插入了5 个数据,后面没有数据插入了,那么就浪费了 95 个数据空间。【浪费空间,不能按需释放和申请空间 】

基于顺序表的缺点,就出现了链表。

相关文章
|
6月前
|
存储 索引
【C进阶】顺序表详解
【C进阶】顺序表详解
|
存储 算法
数据结构与算法之顺序表详解
数据结构与算法之顺序表详解
40 0
|
3月前
|
存储 算法
【数据结构与算法】顺序表
【数据结构与算法】顺序表
24 0
【数据结构与算法】顺序表
|
5月前
|
存储 算法
【C/数据结构与算法】:顺序表的实现
【C/数据结构与算法】:顺序表的实现
35 0
|
5月前
数据结构初阶 顺序表的补充
数据结构初阶 顺序表的补充
27 0
|
6月前
|
存储 人工智能 算法
数据结构与算法:顺序表
数据结构与算法:顺序表
48 1
|
6月前
|
存储 算法
【数据结构与算法】3.顺序表
【数据结构与算法】3.顺序表
|
6月前
|
算法 C语言
数据结构与算法顺序表数组版
博主还在学校,写网络编程特别是后面的线程和多路I/O实在是太费精力,所以博主先把数据结构多跟新一点,也正好把学校的C语言数据结构的作业做了,正好一举两得
43 0
|
6月前
|
存储 算法
【408数据结构与算法】—顺序表的定义(三)
【408数据结构与算法】—顺序表的定义(三)