C语言顺序表:从零开始,解锁数据结构之门!
目录
引言
一、什么是顺序表?
二、静态顺序表
2.1、定义
2.2、代码实现
2.3、静态顺序表的缺点
三、动态顺序表
3.1、定义
3.2、代码实现
四、动态顺序表的操作
4.1、动态顺序表的初始化
4.2、尾插法
4.3、头插法
4.4、尾删法
4.5、头删法
4.6、删除指定位置的数据
4.7、指定位置之前插入数据
4.8、查找
4.9、销毁
结语
引言
“Hello, world!” 也许是你接触编程的第一句代码。但要成为一名合格的程序员,仅仅会写代码是不够的。深入理解数据结构,才能驾驭复杂的数据、设计高效的算法,构建出强大的软件。今天,我们将一起踏上数据结构的探索之旅,从最基础、也是最常用的数据结构——顺序表开始。它像一颗基石,构筑了许多高级数据结构的根基。准备好了吗?拿起你的C语言编译器,让我们一起揭开顺序表的神秘面纱!
一、什么是顺序表?
顺序表,是使用一段连续的存储空间存储数据元素的线性表。其不管在逻辑结构上还是物理结构上,数据的存储都是连续紧挨着的。
二、静态顺序表
2.1、定义
静态顺序表,顾名思义,它的大小是一定的,不可改变的。常见的静态顺序表就是数组,但是这不够严谨,因为我们只知道静态顺序表的大小,却不知道它的有效元素的个数,因此我们使用结构体来表示静态顺序表,动态顺表也是同样的。
2.2、代码实现
typedef int SLDataType;
#define N 7//静态顺序表
typedef struct SeqList
{SLDataType arr[N]; //顺序表主体int size; //有效元素个数
}SL;
注意:
(1)对 int 重新命名为 SLDateType 是为了方便后续修改顺序表的元素类型,如果 int 要替换成 long ,只需要在第一行替换即可。同理,N也是如此。
(2)对结构体重新命名也是为了方便代码后续编写,当然,你也可以单独命名:
typedef SqeList SL;
这二者的重命名方式是等价的
2.3、静态顺序表的缺点
静态顺序表的大小是不可更改的,空间小了不够用,空间大了又浪费内存,具有非常大的局限性。而为了解决这个问题,我们可以使用动态顺序表。
三、动态顺序表
3.1、定义
动态顺序表本质和静态顺序表一样,都是一段连续的存储空间存储数据元素的线性表。不同的是,动态顺序表可以随时调整自己的大小来满足需求,比静态顺序表更加灵活
3.2、代码实现
typedef int SLDataType;typedef struct SeqLsit
{SLDataType* arr; //顺序表主体,后面会开辟空间int size; //顺序表的有效元素个数int capacity; //顺序表的容量
}SL;
动态顺序表由于大小随时变化,因此,它比静态顺序表多了一个参数:容量。
四、动态顺序表的操作
4.1、动态顺序表的初始化
//初始化
void SLInitial( SL* ps)
{ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}
注意:初始化的方式不唯一!
4.2、尾插法
//尾插法
void SLinend(SL* ps ,SLDataType x)
{assert(ps);if (ps->size == ps->capacity){int new_capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//增容SLDataType* str = (SLDataType*)realloc(ps->arr,sizeof(SLDataType) * new_capacity);if (str == NULL){perror("realloc");exit(1);}ps->arr = str;ps->capacity = new_capacity;}ps->arr[ps->size++] = x;
}
首先要判断传入的 ps 是不是空指针?用assert来检查,该函数包含在 <assert.h> 中
在插入之前,需要判断该顺序表的容量是否足够?如果容量足够,则直接插入即可;如果用量不够,需要扩容。一般我们扩容选择扩容到当前容量的2倍或者3倍。扩容的代码形式有很多种,不一定非要跟笔者一致。
在插入完成后,记得要把容量和有效元素个数进行更新!
4.3、头插法
//头插法
void SLin_Head(SL*ps , SLDataType x)
{assert(ps);if (ps->size == ps->capacity){int new_capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;SLDataType* str = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * new_capacity);if (str == NULL){perror("realloc");exit(1);}ps->arr = str;ps->capacity = new_capacity;}for (int i = ps->size; i > 0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}
同样的,需要检查 ps 是否为空,然后再判断是否需要增容?可以把判断是否增容这部分代码写成一个函数,方便后续使用。
4.4、尾删法
//尾删法
void SLDete_end(SL* ps)
{assert(ps && ps->size);ps->size--;
}
除了需要判断 ps 是不是 NULL ,还需要判断 该线性表中有没有元素,没有元素还怎么删呢,对吧?
这里需要注意的是,不能使用 free,对最后一个元素进行 free 处理,这样做会语法报错!free 只能释放完整的空间!
4.5、头删法
//头删法
void SLDete_head(SL* ps)
{assert(ps && ps->size);for (int i = 0; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;
}
用后面的元素对前一个元素进行覆盖即可,最后不要忘了有效个数的更新
4.6、删除指定位置的数据
//删除指定位置的数据
void SLDete_specify(SL* ps, int pose)
{assert(ps && ps->size);assert(pose >= 0 && pose < ps->size);for (int i = pose; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;
}
指定位置后面的元素依次向前覆盖
4.7、指定位置之前插入数据
//指定位置之前插入数据
void SLin_specify_f(SL* ps, int pose, SLDataType x)
{assert(ps);assert(pose >= 0 && pose < ps->size);if (ps->size == ps->capacity){int new_capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;SLDataType* tmd = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * new_capacity);if (tmd == NULL){perror("realloc");exit(1);}ps->arr = tmd;ps->capacity = new_capacity;}for (int i = ps->size; i > pose; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[pose] = x;ps->size++;
}
同样,涉及到添加数据,就需要判断是否需要增容
4.8、查找
//查找
int SLSeek(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->arr[i] == x){return i;}}return -1;
}
4.9、销毁
void SLdestory(SL* ps)
{assert(ps);if (ps->arr)free(ps->arr);ps->arr = NULL;ps->size = ps->capacity = 0;
}
这里释放完后记得将其归空,避免野指针,同时,size和capacity也要置0
结语
恭喜你!你已经掌握了C语言顺序表的核心知识。它虽然是基础,但却是通往数据结构世界的大门。通过实践和不断思考,你会发现顺序表远不止这些。希望这篇博客能激发你对数据结构的兴趣,继续探索更深层次的知识。记住,编程的乐趣在于不断学习和挑战。现在,就拿起你的编译器,把这些知识变成你自己的吧! 下次,我们将会一起探索链表,敬请期待!