顺序表
恭喜大家拿下C语言了,今天我们正式进入数据结构内容,学习数据结构,我们C语言的基础要非常的扎实,非常的牢固才行,如果C语言部分还有地方不太明白,可以食用完博主的C语言专栏,再来学习数据结构噢
强烈建议本篇收藏后食用~
那么这里博主先安利一下一些干货满满的专栏啦!
作者: #西城s
这是我的主页:#西城s
在食用这篇博客之前,博主在这里介绍一下其它高质量的编程学习栏目:
数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏:算法 这里可以说是博主的刷题历程,里面总结了一些经典的力扣上的题目,和算法实现的总结,对考试和竞赛都是很有帮助的!
力扣刷题专栏:Leetcode想要冲击ACM、蓝桥杯或者大学生程序设计竞赛的伙伴,这里面都是博主的刷题记录,希望对你们有帮助!
C的深度解剖专栏:C语言的深度解剖想要深度学习C语言里面所蕴含的各种智慧,各种功能的底层实现的初学者们,相信这个专栏对你们会有帮助的!
顺序表是线性表的一种,除了顺序表之外,线性表还包括链表、栈、队列等数据结构,这些数据结构的详解实现,博主在以前的一些博客中已经详解过了,伙伴们可以通过传送门食用噢~都收录在【数据结构】专栏里了。
在本期博客,博主将带着大家对顺序表实现以下功能:
- 尾插尾删
- 头插头删
- 在任意位置插入和删除
- 查找和修改
同样,我们还是按照分模块编程的习惯,创建3个源文件:
SeqList.c
用于接口的实现
SeqList.h
顺序表结构、接口的声明、头文件
test.c
测试
//动态顺序表
typedef int SLDdataType;
typedef struct SeqList {
SLDdataType* a;
size_t size;//数据个数
size_t capacity;//容量
}SL;
最重要的,无论我们模拟实现哪一种数据结构,我们都要测试,因此在test.c
文件里,我们如果想要进行后序的增删查改等功能,我们都要有一个初始化接口,将我们的表先初始化好,我们后面才有增删查改这些东西可以操作。
具体初始化的思路非常简单,让指针置空,表示现在是空表,将容量和数据个数置成0即可。
void _SeqListInit(SL* ps) {
assert(ps);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
当我们初始化好,或者后续我们将现有分配给我们的内存空间用完之后,我们肯定是要进行增容的,增容我们使用realloc
函数就行了。
判断条件就是:ps->size==ps->capacity
void _SeqList_checkCapacity(SL* ps) {
assert(ps);
if (ps->size == ps->capacity) {
int _newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//这里我们采用二倍增容法
//如果一开始是空表,先给4个容量,后序的再二倍增容
SLDdataType* tmp = (SLDdataType*)realloc(ps->a, _newCapacity * sizeof(SLDdataType));
if (tmp == NULL) {//这些都是realloc函数的用法
//暂且不明白的朋友们可以先补补C语言
perror("_SeqList_checkCapacity::realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = _newCapacity;
}
}
尾插:在表尾插入一个数据
尾删:在表尾删除一个数据
尾插:
这个接口的实现对于我们来说是非常简单的
我们直接在结构体数组——也就是顺序表后面直接再插入就行了
但是,我们要注意,当容量满的时候,我们要增容,才能插入。因此,无论是头插尾插还是任意位置的插入,只要是插入数据,我们都要检查容量!
检查容量我们直接调用_SeqList_checkCapacity()
函数就行了。
void _SeqListPushBack(SL* ps, SLDdataType x) {
assert(ps);
//扩容
_SeqList_checkCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
尾删:
其实尾删不是真正意义的把那个数删除,我们要做的其实非常简单——把ps->size--;
就行了,这样其实相当于我们删除了一个数,因为尾部那个数我们也访问不到了。
注意:
当且仅当,ps->size>0
的时候,我们才可以进行删除操作!
void _SeqListPopBack(SL* ps) {
assert(ps);
//尾删
/*if (ps->size == 0) {
printf("Empty List! err!\n");
}*/
//或
assert(ps->size > 0);
ps->size--;
}
插入了数据之后,为了方便测试,我们写一个打印接口,打印这个顺序表。
void _SeqListPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n");
}
头插:在表头插入一个数据
头删:在表头删除一个数据
头插和头删:
这里的插入和删除,就和尾插尾删有所不同了,因为是顺序表的缘故,每个数据必须连续存放,所以我们在头部进行操作的时候我们必须要做的一件事就是——挪动数据。
其它的注意事项和尾插尾删的一样,思路都非常简单,这里就不赘述了。
头插:
void _SeqListPushFront(SL* ps, SLDdataType x) {
assert(ps);
//增容
_SeqList_checkCapacity(ps);
//挪动数据
int end = ps->size - 1;
while (end >= 0) {
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
头删:
void _SeqListPopFront(SL* ps) {
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size) {
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
看到这里,我们可能会想到一个问题,在头插头删这些操作里,我们还要挪动数据,说明在顺序表里,头插头删的效率是非常低的! 尾插尾删的效率才高,这其实就是顺序表的一个缺陷。其实顺序表,就是vector
的底层实现。其实学习数据结构,我们不能仅限于一种结构,记住一句话:没有任何一种数据结构可以解决任何问题。 因此我们每一种都要学习,在插入删除这些操作里面,我们可以看到,顺序表的效率其实不高,我们的链表才会诞生,链表的插入删除,效率是非常高的。这里附上博主链表部分的传送门。当然,后面我们也会有查找这些接口,查找效率最高的,是树形结构,AVL,红黑树这些,它们的查找效率无敌快。因此,我们要打好基础,认真学习数据结构,学习每一种数据结构的优劣,这样,我们才能进步!
【链表】单链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
【链表】双向链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
思路和其它接口完全一样,只是多了一个参数pos,我们从pos开始操作就行了,挪动数据也是一样的思路–这里我们直接上代码吧!
任意位置插入:
void _SeqListInsert(SL* ps, int pos, SLDdataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
_SeqList_checkCapacity(ps);
int end = ps->size - 1;
while (end >= pos) {
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//有了这个之后,我们要尾插头插都可以复用了
}
任意位置删除:
void _SeqListErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos;
while (begin < ps->size - 1) {
ps->a[begin] = ps->a[begin + 1];
++begin;
}
ps->size--;
思路非常简单:遍历,找到返回下标,找不到返回-1
int _SeqListFind(SL* ps, SLDdataType x) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
if (ps->a[i] == x) {
return i;//找到了
}
}
return -1;//找不到
}
这个接口通常和查找交互使用,找到以后我们可以修改,可以删除,可以插入,等等。
void _SeqListModify(SL* ps, int pos, SLDdataType x) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
当然,我们每实现完一个接口,我们就可以在test.c里面测试了。
SeqList.h
#pragma once
#include
#include
#include
//动态顺序表
typedef int SLDdataType;
typedef struct SeqList {
SLDdataType* a;
size_t size;//数据个数
size_t capacity;//容量
}SL;
void _SeqListInit(SL* ps);//初始化
void _SeqListPrint(SL* ps);
void _SeqList_checkCapacity(SL* ps);
void _SeqListPushBack(SL* ps, SLDdataType x);//尾插
void _SeqListPopBack(SL* ps);//尾删
void _SeqListPushFront(SL* ps, SLDdataType x);//头插
void _SeqListPopFront(SL* ps);//头删
void _SeqListDestroy(SL* ps);
void _SeqListInsert(SL* ps, int pos, SLDdataType x);
void _SeqListErase(SL* ps, int pos);
int _SeqListFind(SL* ps, SLDdataType x);
void _SeqListModify(SL* ps, int pos, SLDdataType x);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void _SeqListInit(SL* ps) {
assert(ps);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void _SeqList_checkCapacity(SL* ps) {
assert(ps);
if (ps->size == ps->capacity) {
int _newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDdataType* tmp = (SLDdataType*)realloc(ps->a, _newCapacity * sizeof(SLDdataType));
if (tmp == NULL) {
perror("_SeqList_checkCapacity::realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = _newCapacity;
}
}
void _SeqListPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n");
}
void _SeqListPushBack(SL* ps, SLDdataType x) {
assert(ps);
//扩容
_SeqList_checkCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
void _SeqListPushFront(SL* ps, SLDdataType x) {
assert(ps);
//增容
_SeqList_checkCapacity(ps);
//挪动数据
int end = ps->size - 1;
while (end >= 0) {
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
void _SeqListPopBack(SL* ps) {
assert(ps);
//尾删
/*if (ps->size == 0) {
printf("Empty List! err!\n");
}*/
//或
assert(ps->size > 0);
ps->size--;
}
void _SeqListPopFront(SL* ps) {
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size) {
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
void _SeqListDestroy(SL* ps) {
assert(ps);
if (ps->a) {
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
}
void _SeqListInsert(SL* ps, int pos, SLDdataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
_SeqList_checkCapacity(ps);
int end = ps->size - 1;
while (end >= pos) {
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//有了这个之后,我们要尾插头插都可以复用了
void _SeqListErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos;
while (begin < ps->size - 1) {
ps->a[begin] = ps->a[begin + 1];
++begin;
}
ps->size--;
}
int _SeqListFind(SL* ps, SLDdataType x) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
if (ps->a[i] == x) {
return i;//找到了
}
}
return -1;//找不到
}
void _SeqListModify(SL* ps, int pos, SLDdataType x) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
test.c
这里的测试伙伴们可以自行来写,不一定按照博主的顺序来测试。
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void _TestSeqList1() {
SL _SList_1;
//初始化
_SeqListInit(&_SList_1);
_SeqListPushBack(&_SList_1, 1);
_SeqListPushBack(&_SList_1, 2);
_SeqListPushBack(&_SList_1, 3);
_SeqListPushBack(&_SList_1, 4);
_SeqListPushBack(&_SList_1, 5);
_SeqListPrint(&_SList_1);
_SeqListPushFront(&_SList_1, 6);
_SeqListPushFront(&_SList_1, 7);
_SeqListPushFront(&_SList_1, 8);
_SeqListPushFront(&_SList_1, 9);
_SeqListPushFront(&_SList_1, 10);
_SeqListPrint(&_SList_1);
}
void _TestSeqList2() {
SL _SList_1;
//初始化
_SeqListInit(&_SList_1);
_SeqListPushBack(&_SList_1, 1);
_SeqListPushBack(&_SList_1, 2);
_SeqListPushBack(&_SList_1, 3);
_SeqListPushBack(&_SList_1, 4);
_SeqListPushBack(&_SList_1, 5);
_SeqListPrint(&_SList_1);
_SeqListPopBack(&_SList_1);
_SeqListPopFront(&_SList_1);
_SeqListPrint(&_SList_1);
_SeqListInsert(&_SList_1, 0, 100);
_SeqListPrint(&_SList_1);
_SeqListErase(&_SList_1, 1);
_SeqListPrint(&_SList_1);
#if 0
int x = 0;
printf("请输入要删除的数字:");
scanf("%d", &x);
int pos = _SeqListFind(&_SList_1, 3);
if (pos != -1) {
_SeqListErase(&_SList_1, pos);
}
#endif
int x = 0;
int y = 0;
printf("请输入要被修改的数字和修改之后的数字:");
scanf("%d %d", &x, &y);
int pos = _SeqListFind(&_SList_1, 3);
if (pos != -1) {
_SeqListModify(&_SList_1, pos, y);
}
_SeqListDestroy(&_SList_1);
}
int main() {
//_TestSeqList1();
_TestSeqList2();
return 0;
}
看到这里,相信伙伴们已经对顺序表已经有了基本了解,掌握了基本的操作接口实现方法。
如果看到这里的你感觉这篇博客对你有帮助,不要忘了收藏,点赞,转发,关注哦。