大家好啊,几日不见,甚是想念,从这一篇文章开始,我们就要进入数据结构了哦,那么我们废话不多说,今天我们一起来搞定顺序表!!!
1. 顺序表概念及结构
顺序表是一种线性结构,它由一组连续的存储单元(通常是数组)组成,数据元素之间的关系是相邻的。顺序表中的元素是按照一定的顺序排列的,可以通过下标来访问和操作各个元素。顺序表的结构简单、易于实现,是一种常见的数据结构之一。
2. 顺序表分类
• 顺序表和数组的区别
◦ 顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接口
静态顺序表
#define MAX_SIZE 10typedef struct {int data[MAX_SIZE];int length;
} StaticList;
在这个例子中,我们定义了一个包含10个整数元素的静态顺序表,其中data数组存储元素,length表示当前顺序表中元素的个数。在使用静态顺序表时,需要注意不要超出预先定义的最大长度,否则可能导致数组越界错误.
动态顺序表
我们一般使用顺序表都是动态顺序表,这样的话才可以进行增删改查的操作,那么我们接下来就来康康这些操作~
3. 实现动态顺序表
#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{SLDataType* a;int size; // 有效数据个数int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);
下面是实现的代码
#define INIT_CAPACITY 4 typedef int SLDataType; typedef struct SeqList { SLDataType* a; int size; // 有效数据个数 int capacity; // 空间容量
} SL;
3.1 初始化
// 初始化
void SLInit(SL* ps) { ps->a = (SLDataType*)malloc(INIT_CAPACITY * sizeof(SLDataType)); if (!ps->a) { exit(EXIT_FAILURE); // 分配内存失败 } ps->size = 0; ps->capacity = INIT_CAPACITY;
}
- 通过 malloc 函数为顺序线性表的数组成员 a 分配内存空间,大小为 INIT_CAPACITY *
sizeof(SLDataType),其中 INIT_CAPACITY 是一个预先定义的常量,表示初始容量。 - 检查内存分配是否成功,如果分配失败(即 ps->a 为 NULL),则调用 exit(EXIT_FAILURE)
函数退出程序。 - 将顺序线性表的 size 成员初始化为 0,表示当前表中没有元素。
- 将顺序线性表的 capacity 成员初始化为INIT_CAPACITY,表示表的初始容量为 INIT_CAPACITY。
3.2 销毁
void SLDestroy(SL* ps) { free(ps->a); ps->a = NULL; ps->size = 0; ps->capacity = 0;
}
- 调用 free 函数释放顺序线性表的数组成员 a 所占用的内存空间,这样可以避免内存泄漏。
- 将顺序线性表的 a 成员指针设置为 NULL,这是一种良好的习惯,可以避免出现野指针访问的问题。
- 将顺序线性表的 size 成员设为 0,表示表中不再有元素。
- 将顺序线性表的 capacity 成员设为 0,表示表的容量为 0。
3.3 打印
void SLPrint(SL* ps) { for (int i = 0; i < ps->size; i++) { printf("%d ", ps->a[i]); } printf("\n");
}
这个没什么好说的吧哈哈哈
3.4 扩容
void SLCheckCapacity(SL* ps) { if (ps->size == ps->capacity) { ps->capacity *= 2; SLDataType* temp = (SLDataType*)realloc(ps->a, ps->capacity * sizeof(SLDataType)); if (!temp) { exit(EXIT_FAILURE); // 扩容失败 } ps->a = temp; }
}
- 判断顺序线性表的当前大小(size)是否等于容量(capacity),如果相等,则表示表中元素已经填满,需要进行扩容操作。
- 将顺序线性表的容量 capacity 扩大为原来的两倍,即 ps->capacity *= 2;,这里采用了简单的扩容策略。
- 调用 realloc 函数重新分配内存空间,将原来的数据拷贝到新的空间中,大小为 ps->capacity *
sizeof(SLDataType)。 - 检查是否成功分配新的内存空间,如果分配失败(即 temp 为 NULL),则调用 exit(EXIT_FAILURE)
函数退出程序。 - 将新分配的内存空间的地址赋值给顺序线性表的数组成员 a,完成扩容操作。
3.5 头部插入
void SLPushFront(SL* ps, SLDataType x) { SLCheckCapacity(ps); for (int i = ps->size; i > 0; i--) { ps->a[i] = ps->a[i - 1]; } ps->a[0] = x; ps->size++;
}
- 调用 SLCheckCapacity 函数,确保顺序线性表有足够的容量来存储新的元素。
- 从顺序线性表的最后一个元素开始,依次将元素向后移动一个位置,为新元素腾出空间。这里使用循环从 ps->size
开始,依次向前移动元素,直到第一个元素。 - 将要插入的新元素 x 放入顺序线性表的第一个位置,即头部位置。
- 增加顺序线性表的大小 size,表示表中元素数量增加了一个。
3.6 头部删除
void SLPopFront(SL* ps) { if (ps->size == 0) { return; } for (int i = 0; i < ps->size - 1; i++) { ps->a[i] = ps->a[i + 1]; } ps->size--;
}
- 判断顺序线性表的大小(size)是否为0,如果是空表(size == 0),则直接返回,不进行删除操作。
- 从顺序线性表的第一个元素开始,依次将元素向前移动一个位置,覆盖掉原来的元素。这里使用循环从0开始,依次向后移动元素,直到倒数第二个元素。
- 减少顺序线性表的大小 size,表示表中元素数量减少了一个。
3.7 尾部插入
void SLPushBack(SL* ps, SLDataType x) { SLCheckCapacity(ps); ps->a[ps->size] = x; ps->size++;
}
- 调用 SLCheckCapacity 函数,确保顺序线性表有足够的容量来存储新的元素。
- 将要插入的新元素 x 放入顺序线性表当前大小(size)的位置,即在尾部插入。
- 增加顺序线性表的大小 size,表示表中元素数量增加了一个。
3.8 尾部删除
不用说吧~~~
void SLPopBack(SL* ps) { if (ps->size == 0) { return; } ps->size--;
}
3.9 指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) { if (pos < 0 || pos > ps->size) { return; // 插入位置不合理 } SLCheckCapacity(ps); for (int i = ps->size; i > pos; i--) { ps->a[i] = ps->a[i - 1]; } ps->a[pos] = x; ps->size++;
}
- 判断插入位置 pos 是否合理,即必须满足 pos >= 0 且 pos <= ps->size,否则直接返回,不进行插入操作。
- 调用 SLCheckCapacity 函数,确保顺序线性表有足够的容量来存储新的元素。
- 从顺序线性表的最后一个元素开始,依次将元素向后移动一个位置,为新元素腾出空间。这里使用循环从 ps->size开始,依次向前移动元素,直到插入位置 pos。
- 将要插入的新元素 x 放入顺序线性表的指定位置 pos。
- 增加顺序线性表的大小 size,表示表中元素数量增加了一个.
3.10 指定位置删除数据
void SLErase(SL* ps, int pos) { if (pos < 0 || pos >= ps->size) { return; // 删除位置不合理 } for (int i = pos; i < ps->size - 1; i++) { ps->a[i] = ps->a[i + 1]; } ps->size--;
}
- 判断删除位置 pos 是否合理,即必须满足 pos >= 0 且 pos < ps->size,否则直接返回,不进行删除操作。
- 从指定位置 pos 开始,将后面的元素依次向前移动一个位置,覆盖掉要删除的元素。这里使用循环从 pos
开始,依次将元素向前移动,直到倒数第二个元素。 - 减少顺序线性表的大小 size,表示表中元素数量减少了一个,相当于删除了一个元素。
3.11 查找数据
int SLFind(SL* ps, SLDataType x) { for (int i = 0; i < ps->size; i++) { if (ps->a[i] == x) { return i; // 返回数据下标 } } return -1; // 没有找到数据
}
4. 基于动态顺序表实现通讯录项目
相信大家掌握了上面的操作后,实现通讯录并不是啥难事,肖恩在这也不细细讲解了,希望大家理解哦~~~
由于这个通讯录的代码有一点点多,放在这里大家也不好学习,我直接放个链接吧,大家直接点开就欧克了,也方便copy.
通讯录
5. 顺序表经典算法
这是两个有关顺序表的经典的算法的,大家点开链接可以做做,同样,我把代码放在这,大家自行悟道
经典算法OJ题1: 移除元素
int removeElement(int* nums, int numsSize, int val) {int i, j;for(i = 0, j = 0; i < numsSize; i++){if(nums[i] != val){nums[j] = nums[i];j++;}}return j;
}
经典算法OJ题2: 合并两个有序数组
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
int p1 = m - 1;int p2 = n - 1;int p = m + n - 1;while(p1 >= 0 && p2 >= 0){if(nums1[p1] > nums2[p2]){nums1[p] = nums1[p1];p1--;} else {nums1[p] = nums2[p2];p2--;}p--;}while(p2 >= 0){nums1[p] = nums2[p2];p2--;p--;}
}
大家要是有比这个好的方法那就更好了!还是比肖恩强的哈哈哈
6. 顺序表的问题及思考*
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?
下期预告
那当然是…Linked List