顺序表的实现与介绍及部分工程思想的思考

2526 字
13 分钟
顺序表的实现与介绍及部分工程思想的思考

——————————本文旨在讨论和探究计算机知识,欢迎指正———————————— 首先,我们来聊聊什么是线性表: 线性表,顾名思义,是一种具有线性结构的数据排布形式 ,是一个类似数组的东西,在逻辑上一个接一个有序的整块链接,区别于链表的无序分布链接。 如图,我们可以得出,顺序表是一条线一样的结构,环环相扣,存在唯一的第一个元素(表头),也存在唯一的最后一个元素(表尾)。除了表头和表位,每个元素都有一个前驱和后继。 特别的,在c/c++里面,我们的数组就是一个很好的顺序结构,但是它是静态的,无法扩容和插入,这是因为数组是在栈空间里面实现的,所谓栈空间,就是申请后编译器自动分配的空间,这个空间是有限的,无法进行扩容,但是好在方便申请。 而我们今天讨论的就是在堆空间上实现的动态顺序结构: 所谓堆空间,就是程序员操作的空间,你可以在里面实现各种结构变化,扩容,插入等等… 如图: 下面,让我们开始顺序表的实现吧! 首先,我们要定义表头: 类似数组的结构,我们的顺序表是要在堆里面申请一块连续的空间。那么,这个空间的开始地址就很重要了。回顾数组,例如我们定义:

int a[3]={0,1,2};
printf("%d",a+1);
//这时,会输出1的答案,那么a其实就是数组a的最开始的地址,类比表头

有趣的是,我们在使用c++的STL工具时,vectorvc所定义的就是一个表头:

这里是我们定义的表头的结构体: 1、element 是一种重命名后的变量形式,它的好处在于我们typedef后如果某天发现数据存储形式错误,我们可以直接改element 而不是在每段每段的程序里面找和抠,极大减少了工作量。同时,Element_t也是工程里面常用的一种 定义模式,“_t”有种正确规范的意味。 2、pos是我们动态的“数据个数”,也是“下一个是第几个数据”,方便定位,极大优化了查找效率。 3、capacity 是顺序表空间的容量个数,表征最多存的数量,扩容的时候常用。 代码实现:

typedef struct {
Element_t *data; // 存储表中数据空间的首地址
int pos; // 指向数据空间的待插入的位置,也是空间里数据个数的表示
int capacity; // 表中目前最大的存储容量
} SEQTable_t;

下面,我们讲讲工程思想: 一般的,我们在写代码的时候,总是一股脑的把所有函数和代码放在一个文件里面,但是这样对多文件编程和重复调用很不友好,我们得每次使用都在子函数里面在写一遍,很麻烦且容易错,下面本文将简单介绍一下常用方法:

  • 关于多文件编程,我们可以做到如下几点,来使得函数在同一个文件夹内协同作用:

以上是用clion软件为例,在CMakeLists.txt中的add_executable()里面加上你希望一起需用的文件名,就可以使用了;当然,这里我们的前导是文件打开的文件夹的名字:

  • add_executable**:这是 CMake 的内置命令,作用是告诉 CMake 要创建一个可执行文件。
  • seqTable:此为可执行文件的名称,编译完成后,在构建目录下会生成名为 seqTable(Windows 系统下是 seqTable.exe)的可执行文件。
  • 01_seqTable/main.c:表示源文件的路径,这里指明使用 01_seqTable 目录下的 main.c 文件来编译生成可执行文件。

如果你要使用中文,但是你用的是Windows系统,记得把UFT-8改为GBS国标,由于编译方式的不同,会出现乱码。(Linux和Mac不会有这种问题) 2、头文件的类编写: 如果在一个文件夹下我们进行头文件.h的编写,那么下次使用(在文件夹内)我们就可以直接用头文件里面的东西了,而不用在main.c文件里面再写一遍。 有些人可能会想一个疑问:既然.h文件里面可以编写结构体而且能直接被.c使用,为什么不在.h的文件里面编写函数,这样不是更方便吗?回答:我们不要把.h和.c看成两部分,也不要只认为.h只在.h的目录下实现。因为——.h只要能被编译器读到,那这个.c就可以被正常编译,所以.h放哪里都可以,只是要告诉编译器这个.h在哪里。一般像gcc有-I选项,VC那块可以右键属性添加查找头文件路径的。 .h相当于是一个显性目录的作用,.c是我具体实现的代码,我可能不想公开给别人,但是别人想用我的函数,我就告诉他我函数的申明,然后我把.c编译成动态库给他。就像printf那样,你根本就不知道咋实现的,但是可以轻松使用它。我们在#include “a.h“的时候,没有前缀,直接写a.h就表示在当前目录下查找是否有这个文件,只要头文件能找到,就可以直接用函数了。 这是一个顺序表的.h文件的实现:

//
// Created by 水木 on 25-5-30.
//
#ifndef SEQ_TABLE_H
#define SEQ_TABLE_H
//定义一个数据空间存储类型
typedef int ElemType_t;//这里尾部加上_t指的规范的重命名数据类型;
typedef struct {
ElemType_t *data;//首地址
int pos;//待插入位置
int capacity;//最大存储容量
}SEQTable_t;
//表头在堆上申请,提供给其他函数使用,需要释放的接口;
SEQTable_t *createSeqTable(int n);//申请内存
void releaseSeqTable(SEQTable_t *table);//释放内存
int pushbackSeqTable(SEQTable_t* table, ElemType_t val);//尾插法
static int enlargeSeqTable(SEQTable_t *table);//扩容
int insertPosSeqTable(SEQTable_t *table, int index, ElemType_t value);//中间插入
int findSeqTable(const SEQTable_t* table, ElemType_t val);//找寻函数
void deleteSeqTable(SEQTable_t* table,ElemType_t val);//删除函数
void ShowSeqTable(const SEQTable_t *table);//此时用静态变量
#endif //SEQ_TABLE_H

下面,我们回到顺序表的实现上来 : 首先,我们实现申请表头的函数:

SEQTable_t *createSeqTable(int n) {
SEQTable_t* table = NULL;
table = malloc(sizeof(SEQTable_t));
if (table == NULL) {
fprintf(stderr,"SeqTable malloc defeat!\n");//申请失败,释放表头
return NULL;
}
table->data = malloc(sizeof(ElemType_t)* n);
if (table->data==NULL) {
fprintf(stderr,"SeqTable data malloc defeat!\n");
free(table);//申请结构体内数据数组空间失败,释放表头
return NULL;
}
table->pos=0;//初始化pos;
table->capacity=n;//初始化capacity
return table;
}

有了表头,就要有释放函数:

void releaseSeqTable(SEQTable_t *table) {
//如果有表头空间就释放
if (table) {
//如果有数据空间就释放
if (table->data) {
free(table->data);
}
free(table);
}
}

下面,我们来实现增删改查: 首先,是增:

//这里使用static有好处,是静态继承的,只会在内部改变,不会因为外界干扰而紊乱,增加安全性
static int enlargerTable(SEQTable_t *table) {
// 1. 再申请一个新空间,初始化申请的空间
Element_t *tmp = malloc(sizeof(Element_t) * table->capacity * 2);
if (tmp == NULL) {
printf("enLargerTable malloc failed!\n");
return -1;
}
// 2. 拷贝老内容到新空间,for循环一个个拷贝,直接空间拷贝
memcpy(tmp, table->data, sizeof(Element_t) * table->pos);//string.h下的函数memcpy(空间名,赋值位置,赋值个数*单位大小=总体积)
// 3. 更新表头,释放老空间
table->capacity *= 2;//两倍扩容是一个习惯性行为
free(table->data);//释放原空间
table->data = tmp;//将原空间指针指向现有空间
return 0;
}
//特别的,空间扩容是先创建一个副本再copy上去,最后释放空间,为防止空间泄露,所以不在原来空间上扩容(可能原来空间的下面几个空间已经被分配给其他板块,如果在原空间上强项扩容可能导致其他版块数据丢失,程序崩溃)
int pushbackSeqTable(SEQTable_t* table, ElemType_t val) {
if (table == NULL) {
fprintf(stderr,"SeqTable malloc defeat\n");//排除表头不存在的时候增
return -1;
}
//这里是集成了扩容函数enlaegeSeqTable(table)的增添,如果满了的条件下就增,否则跳过不判断,扩容函数返回0的时候是扩容成功所以&&满足true的时候输出扩容失败:
if (table->pos>=table->capacity&&enlaegeSeqTable(table)) {
printf("badbad,cant enlarge place!\n");
return -1;
}
table->data[table->pos]=val;//赋值,扩容成功
++table->pos;//pos右移,指向下一个
return 0;
}

其次,是删和查的实现:

int findSEQTable(const SEQTable_t *table, ElemType_t val) {
for (int i=0;ipos;++i) {
if (table->data[i]==val) {
return i;
}
}
return -1;
}//删查一体,查找到了才能删;
int deleteSeqTable(SEQTable_t* table, Element_t value) {
// 1. 查找value在数据结构中的索引位置
int index = findSeqTable(table, value);
//未找到则输出“Not find %d element!”
if (index == -1) {
printf("Not find %d element!\n", value);
return -1;
}
// 2. 删除这个元素,把[index + 1, pos) 搬移覆盖到,把后一个给前一个
for (int i = index + 1; i pos; ++i) {
table->data[i - 1] = table->data[i];
}
--table->pos;
return 0;
}

最后,是改的实现(插入):

int insertPosSeqTable(SEQTable_t* table, int index, Element_t value) {
// 1. 空间有效性的判断
if (index table->pos) {
printf("index Invalid!\n");
return -1;
}
// 顺序表进行扩容
if (table->pos >= table->capacity && enlargerTable(table)) {
printf("....\n");
return -1;
}
// 2. 搬移数据为新数据预留位置 从后往前 data[i + 1] = data[i];
// [pos-1, index] 使用for --操作
for (int i = table->pos - 1; i >= index; --i) {
table->data[i + 1] = table->data[i];
}
table->data[index] = value;
++table->pos;
return 0;
}

噢,对了,还有一个展现函数:

void showSeqTable(const SEQTable_t* table) {
if (table == NULL) {
return;
}//没有表,没有展现的
for (int i = 0; i pos; ++i) {
printf("%d\t", table->data[i]);
}//制表符\t,看起来更整齐
// deleteSeqTable(table, )
printf("\n");
}

下面,是我们的测试函数,在main.c里面测试:

void test01() {
SEQTable_t *table01=createSeqTable(10);
if (table01==NULL) {
return;
}
for (int i=0;i<5;i++) {
pushbackSeqTable(table01,100+i);
}
for (int i=5;i<10;i++) {
insertSeqTable(table01,i-2,100-i);
}
showSeqTable(table01);
releaseSeqTable(table01);
}
int main() {
test01();
return 0;
}

顺序表的总结: 希望对您有所帮助! ——————————————————欢迎指正————————————————————

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
顺序表的实现与介绍及部分工程思想的思考
https://firefly.cuteleaf.cn/posts/knowledge/顺序表的实现与介绍及部分工程思想的思考/
作者
Firefly
发布于
2026-06-21
许可协议
CC BY-NC-SA 4.0
相关文章 智能推荐
1
链表的实现与介绍
数据结构 ——————————————本文旨在交流计算机知识,欢迎指正————————————— 首先,我们回顾一下上一期讲的顺序表: 顺序表是一种物理结构上连续的,一整块连续空间的逻辑表,但是,试想一下,如果每次我们都是申请在一块整块空间里面的顺序表,但是我们并不知道堆空间里面我们malloc一块空间之后我...
2
数据结构顺序队列及链式队列
数据结构 ————————————本栏目旨在讨论计算机知识,欢迎交流————————————— 上一章,我们讨论了顺序栈和链式栈,这一章,我们将讲解线性结构的最后一个部分——顺序队列和链式队列。相比于栈结构的同端进出,就像原肠生物,而队列则是尾部入队和头部出队,就像后口生物一样。 如图: 那么,如何来实现入...
3
数据结构之树及树的存储
数据结构 ——————————————本栏目旨在交流计算机知识————————————————— 上一章节,我们介绍了线性的数据结构:表,栈,队列。接下来,我们将进入下一章节——树结构的学习。 首先,我们先介绍一下树的基本概念: 结点:使⽤树结构存储的每一个数据元素都被称为“结点”。例如图中的A就是一个结点。...
4
数据结构之顺序栈与链式栈
数据结构 ——————————————本文旨在交流计算机技术,欢迎指正————————————— 上一大章,我们深入讲解了链表的内容,这一章,我们详细介绍一种较为简单的数据结构———栈。 首先,我们介绍栈是什么:栈是一种单向的结构,其中栈的底部是固定的,不可以进出,我们可操作的部分只有栈顶,来进行出入栈操作。...
5
数据结构二叉树的基本介绍
数据结构 ————————————本文旨在讨论与学习计算机知识,欢迎交流———————————— 上一章,我们讲解了树结构的综述导论,那么,现在我们来深入了解一下树结构中最常用研究的结构——二叉树结构(上一章的扩展——孩子兄弟表示法就是利用的二叉树的原理)。 以上是关于普通二叉树的基本概念,我们可以结合上一...
随机文章 随机推荐
Profile Image of the Author
Firefly
Hello, I'm Firefly.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
33
分类
7
标签
25
总字数
56,127
运行时长
0
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.12.3
文章许可
CC BY-NC-SA 4.0

文章目录