线性表顺序存储的优缺点
优点
- 无需为表中的逻辑关系增加额外的存储空间,利用连续的内存单元存储数据,存储密度高。
- 支持 随机访问,通过下标可在 O(1) 时间复杂度内定位元素(如数组按索引取值),查询效率稳定。
缺点
- 插入 / 删除低效:若操作位置非表尾,需移动大量元素(如插入到第 i 位,需移动后续 n−i 个元素 ),时间复杂度为 O(n) ,数据量大时性能差。
- 空间灵活性不足:
- 静态分配(如固定长度数组)易因数据超容量 “溢出”,或因预分配过多造成内存浪费;
- 动态扩容(如编程语言中列表的自动扩容)需重新申请内存、拷贝数据,会带来额外开销。
顺序表操作函数
SeqList.h
// 条件编译:防止头文件被重复包含
// 如果没有定义 _SEQLIST_H_ 这个宏,就执行下面的代码,直到 #endif
#ifndef _SEQLIST_H_
#define _SEQLIST_H_ // 定义这个宏,标记头文件已被包含// 1. 定义人员信息结构体,并起别名为 DATATYPE(数据类型)
typedef struct person
{char name[32]; // 姓名:字符数组,最多存31个字符(含结束符'\0')char sex; // 性别:单个字符(如 'M' 表示男,'F' 表示女)int age; // 年龄:整数类型int score; // 分数:整数类型(如考试成绩)
} DATATYPE; // 别名 DATATYPE,后续可直接用 DATATYPE 代替 struct person// 2. 定义顺序表结构体,用于管理一组 DATATYPE 类型的数据
typedef struct list
{DATATYPE *head; // 数组指针:指向存储人员信息的数组的首地址int tlen; // total length:数组的总容量(最多能存多少个元素)int clen; // current length:当前已存储的有效元素个数
} SeqList; // 别名 SeqList,后续可直接用 SeqList 代替 struct list// 3. 函数声明:告诉编译器这些函数的存在、参数和返回值,具体实现在 .c 文件中// 创建顺序表
// 参数:len 为顺序表的初始容量
// 返回值:成功返回创建好的顺序表指针(SeqList*),失败返回 NULL
SeqList *CreateSeqList(int len);// 显示顺序表中的所有数据
// 参数:list 为要显示的顺序表指针
// 返回值:0 表示成功
int ShowSeqList(SeqList *list);// 向顺序表的尾部插入元素
// 参数:list 为目标顺序表,data 为要插入的人员信息(DATATYPE 指针)
// 返回值:0 表示成功,1 表示失败(如表已满)
int InsertTailSeqList(SeqList *list, DATATYPE *data);// 判断顺序表是否已满
// 参数:list 为目标顺序表
// 返回值:1 表示已满,0 表示未满
int IsFullSeqList(SeqList *list);// 判断顺序表是否为空
// 参数:list 为目标顺序表
// 返回值:1 表示为空,0 表示非空
int IsEmptySeqList(SeqList *list);// 按指定位置插入元素
// 参数:list 为目标顺序表,data 为要插入的信息,pos 为插入的下标位置
// 返回值:0 表示成功,1 表示失败(如位置无效或表已满)
int InsertPosSeqList(SeqList *list, DATATYPE *data, int pos);// 按姓名查找元素在顺序表中的位置
// 参数:list 为目标顺序表,name 为要查找的姓名
// 返回值:成功返回元素下标(0 开始),失败返回 -1
int FindSeqList(SeqList *list, char *name);// 按姓名修改顺序表中的元素
// 参数:list 为目标顺序表,old 为要修改的姓名,newdata 为新的信息
// 返回值:0 表示成功,1 表示失败(如未找到姓名)
int ModifySeqList(SeqList *list, char *old, DATATYPE *newdata);// 清空顺序表(仅重置有效元素个数,不释放内存)
// 参数:list 为目标顺序表
// 返回值:0 表示成功
int ClearSeqList(SeqList *list);// 销毁顺序表(释放所有分配的内存)
// 参数:list 为目标顺序表
// 返回值:0 表示成功
int DestroySeqList(SeqList *list);// 按姓名删除顺序表中的元素
// 参数:list 为目标顺序表,name 为要删除的姓名
// 返回值:0 表示成功,1 表示失败(如未找到姓名或表为空)
int DeleteSeqList(SeqList *list, char *name);// 获取顺序表的有效元素个数
// 参数:list 为目标顺序表
// 返回值:当前有效元素的个数(clen)
int GetSizeSeqList(SeqList *list);// 获取顺序表中指定位置的元素
// 参数:list 为目标顺序表,pos 为要获取的元素下标
// 返回值:成功返回元素的指针(DATATYPE*),失败返回 NULL
DATATYPE* GetItemSeqlist(SeqList* list, int pos);#endif // 结束条件编译,与开头的 #if !defined(_SEQLIST_H_) 对应
SeqList.c
#include "seqlist.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/*** @brief 创建一个顺序表* @param len 顺序表的初始容量(最多能存储的元素个数)* @return 成功返回顺序表指针,失败返回NULL*/
SeqList *CreateSeqList(int len)
{// 1. 为顺序表结构体本身分配内存(管理用的"文件夹")SeqList *sl = malloc(sizeof(SeqList));if (NULL == sl) // 判断内存分配是否失败{perror("CreateSeqList malloc error\n"); // 打印错误信息return NULL;}// 2. 为顺序表中的数组分配内存(存储数据的"表格")// 数组元素类型是DATATYPE,共分配len个元素的空间sl->head = malloc(sizeof(DATATYPE) * len);if (NULL == sl->head) // 判断数组内存分配是否失败{perror("CreateSeqList malloc2 error\n");free(sl); // 先释放已分配的结构体内存,避免内存泄漏return NULL;}// 3. 初始化顺序表的属性sl->tlen = len; // 总容量设为用户指定的lensl->clen = 0; // 初始有效元素个数为0(空表)return sl; // 返回创建好的顺序表
}/*** @brief 向顺序表尾部插入元素* @param list 目标顺序表* @param data 要插入的元素(人员信息)* @return 0表示成功,1表示失败*/
int InsertTailSeqList(SeqList *list, DATATYPE *data)
{// 1. 先判断顺序表是否已满if (IsFullSeqList(list)){printf("seqlist is full\n");return 1;}// 2. 把数据复制到数组的末尾(当前有效元素的下一个位置)// list->clen是当前有效元素个数,也是下一个元素的下标memcpy(&list->head[list->clen], data, sizeof(DATATYPE));// 3. 有效元素个数加1list->clen++;return 0; // 插入成功
}/*** @brief 判断顺序表是否已满* @param list 目标顺序表* @return 1表示已满,0表示未满*/
int IsFullSeqList(SeqList *list)
{// 有效元素个数等于总容量时,表已满return list->clen == list->tlen;
}/*** @brief 打印顺序表中的所有元素* @param list 目标顺序表* @return 0表示成功*/
int ShowSeqList(SeqList *list)
{int len = GetSizeSeqList(list); // 获取有效元素个数int i = 0;// 遍历所有有效元素并打印for (i = 0; i < len; ++i){printf("name:%s sex:%c age:%d score:%d\n", list->head[i].name, // 姓名list->head[i].sex, // 性别list->head[i].age, // 年龄list->head[i].score); // 分数};return 0;
}/*** @brief 获取顺序表的有效元素个数* @param list 被查询的顺序表* @return 顺序表中有效元素的个数*/
int GetSizeSeqList(SeqList *list)
{return list->clen; // 直接返回当前有效元素个数
}/*** @brief 判断顺序表是否为空* @param list 目标顺序表* @return 1表示为空,0表示非空*/
int IsEmptySeqList(SeqList *list)
{return 0 == list->clen; // 有效元素个数为0时为空
}/*** @brief 按位置插入元素(在指定下标处插入)* @param list 待插入的顺序表* @param data 需要插入的数据* @param pos 插入的位置(数组下标)* @return 0表示成功,1表示失败*/
int InsertPosSeqList(SeqList *list, DATATYPE *data, int pos)
{// 1. 检查顺序表是否已满if (IsFullSeqList(list)){printf("seqlist is full\n");return 1;}// 2. 检查插入位置是否合法int len = GetSizeSeqList(list);// 合法位置:0 <= pos <= len(可以插在末尾)if (pos < 0 || pos > len){printf("pos is incorrect\n");return 1;}// 3. 从最后一个元素开始,依次向后移动一位,给新元素腾出位置// (从后往前移,避免覆盖还没移动的元素)for (int i = list->clen; i > pos; i--){memcpy(&list->head[i], &list->head[i - 1], sizeof(DATATYPE));}// 4. 把新元素插入到pos位置memcpy(&list->head[pos], data, sizeof(DATATYPE));// 5. 有效元素个数加1list->clen++;return 0;
}/*** @brief 按名字查找元素在顺序表中的位置* @param list 待查询的顺序表* @param name 要查询的姓名* @return 成功返回元素下标(0~len-1),失败返回-1*/
int FindSeqList(SeqList *list, char *name)
{int len = GetSizeSeqList(list);// 遍历所有元素,对比姓名(字符串比较用strcmp)for (int i = 0; i < len; i++){// strcmp返回0表示两个字符串相等if (0 == strcmp(list->head[i].name, name)){return i; // 找到,返回下标}}return -1; // 没找到
}/*** @brief 获取顺序表中指定位置的元素* @param list 目标顺序表* @param pos 要获取的元素位置(下标)* @return 成功返回元素的指针,失败返回NULL*/
DATATYPE* GetItemSeqlist(SeqList* list, int pos)
{int len = GetSizeSeqList(list);// 检查位置是否合法(0 <= pos < len)if (pos < 0 || pos >= len) // 注意这里是>=,因为最大下标是len-1{printf("pos is incorrect\n");return NULL;}// 返回pos位置元素的地址return &list->head[pos];
}/*** @brief 按姓名修改顺序表中的元素* @param list 目标顺序表* @param oldname 要修改的元素的姓名* @param newdata 新的元素数据* @return 0表示成功,1表示失败*/
int ModifySeqList(SeqList *list, char *oldname, DATATYPE *newdata)
{// 1. 先查找旧元素的位置int ret = FindSeqList(list, oldname);if(-1 == ret) // 没找到{printf("modify failure...\n");return 1;}// 2. 用新数据覆盖旧数据memcpy(&list->head[ret], newdata, sizeof(DATATYPE));return 0;
}/*** @brief 清空顺序表(仅清空数据,不释放内存)* @param list 目标顺序表* @return 0表示成功*/
int ClearSeqList(SeqList *list)
{// 直接将有效元素个数设为0,原数据会被后续插入覆盖list->clen = 0 ;return 0;
}/*** @brief 销毁顺序表(释放所有分配的内存)* @param list 目标顺序表* @return 0表示成功*/
int DestroySeqList(SeqList *list)
{// 先释放存储数据的数组内存free(list->head);// 再释放顺序表结构体本身的内存free(list);return 0;
}/*** @brief 按姓名删除顺序表中的元素* @param list 目标顺序表* @param name 要删除的元素的姓名* @return 0表示成功,1表示失败(未找到或表为空)*/
int DeleteByNameSeqList(SeqList *list, char *name)
{// 1. 先判断表是否为空,空表无需删除if (IsEmptySeqList(list)){printf("seqlist is empty, cannot delete\n");return 1;}// 2. 查找要删除的元素位置(调用已有的FindSeqList函数)int pos = FindSeqList(list, name);if (pos == -1) // 未找到对应姓名的元素{printf("name '%s' not found, delete failed\n", name);return 1;}// 3. 从删除位置的下一个元素开始,依次向前移动一位,覆盖要删除的元素// (从前往后移,避免覆盖还未移动的元素)for (int i = pos; i < list->clen - 1; i++){// 用后一个元素覆盖当前元素(复制整个DATATYPE结构体)memcpy(&list->head[i], &list->head[i + 1], sizeof(DATATYPE));}// 4. 有效元素个数减1(表的长度减1)list
->clen--;printf("delete '%s' successfully\n", name);return 0;
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h"int main(int argc, char** argv)
{// 1. 创建容量为5的顺序表SeqList* sl = CreateSeqList(5);if (sl == NULL) // 增加创建失败的判断{printf("创建顺序表失败,程序退出\n");return 1; // 非0表示程序异常退出}// 2. 定义测试数据(5个人员信息,与顺序表容量匹配)DATATYPE data[] = {{"zhangsan", 'm', 20, 80}, // 下标0{"lisi", 'f', 23, 84}, // 下标1{"wangmaizi", 'f', 32, 90}, // 下标2{"guanerge", 'm', 50, 91}, // 下标3{"liubei", 'm', 51, 92} // 下标4};// 3. 尾插3个元素(zhangsan、lisi、wangmaizi)printf("-----初始尾插3个元素后-----\n");InsertTailSeqList(sl, &data[0]);InsertTailSeqList(sl, &data[1]);InsertTailSeqList(sl, &data[2]);ShowSeqList(sl); // 显示当前表中数据// 4. 在位置1插入guanerge(原lisi的位置将后移)printf("\n-----在位置1插入guanerge后-----\n");int insert_ret = InsertPosSeqList(sl, &data[3], 1);if (insert_ret != 0){printf("插入失败!\n");}else{ShowSeqList(sl); // 此时表中应有4个元素}// 5. 查找不存在的姓名(li1si)printf("\n-----查找不存在的姓名li1si-----\n");char want_name[50] = "li1si";int find_ret = FindSeqList(sl, want_name);if (find_ret == -1){printf("未找到姓名为 %s 的人员\n", want_name);}else{DATATYPE* tmp = GetItemSeqlist(sl, find_ret);printf("找到人员:%s,分数:%d\n", tmp->name, tmp->score);}// 6. 查找存在的姓名(lisi)printf("\n-----查找存在的姓名lisi-----\n");strcpy(want_name, "lisi"); // 修改要查找的姓名find_ret = FindSeqList(sl, want_name);if (find_ret == -1){printf("未找到姓名为 %s 的人员\n", want_name);}else{DATATYPE* tmp = GetItemSeqlist(sl, find_ret);printf("找到人员:%s,年龄:%d,分数:%d\n", tmp->name, tmp->age, tmp->score);}// 7. 修改lisi的信息为liubeiprintf("\n-----修改lisi为liubei后-----\n");int modify_ret = ModifySeqList(sl, "lisi", &data[4]);if (modify_ret != 0){printf("修改失败!\n");}else{ShowSeqList(sl); // 查看修改后的结果}// 8. 新增:删除姓名为zhangsan的元素printf("\n-----删除zhangsan后-----\n");int delete_ret = DeleteSeqList(sl, "zhangsan");if (delete_ret != 0){printf("删除失败!\n");}else{ShowSeqList(sl); // 查看删除后的结果}// 9. 销毁顺序表,释放内存DestroySeqList(sl);sl = NULL; // 避免野指针(销毁后指针置空)// 调试用:暂停程序查看输出(Windows系统)// system("pause");return 0;
}
运行结果
-----初始尾插3个元素后-----
name:zhangsan sex:m age:20 score:80
name:lisi sex:f age:23 score:84
name:wangmaizi sex:f age:32 score:90-----在位置1插入guanerge后-----
name:zhangsan sex:m age:20 score:80
name:guanerge sex:m age:50 score:91
name:lisi sex:f age:23 score:84
name:wangmaizi sex:f age:32 score:90-----查找不存在的姓名li1si-----
未找到姓名为 li1si 的人员-----查找存在的姓名lisi-----
找到人员:lisi,年龄:23,分数:84-----修改lisi为liubei后-----
name:zhangsan sex:m age:20 score:80
name:guanerge sex:m age:50 score:91
name:liubei sex:m age:51 score:92
name:wangmaizi sex:f age:32 score:90-----删除zhangsan后-----
name:guanerge sex:m age:50 score:91
name:liubei sex:m age:51 score:92
name:wangmaizi sex:f age:32 score:90