前言:
本文主要提供顺序表的C语言实现代码,包括顺序表的初始化,扩容、判空、判满、插入元素、删除元素、遍历元素、查询元素、修改元素、统计元素个数、清空以及释放,共计十二个功能函数的代码实现。(代码含详细的注释,可根据注释快速理解各个功能模块的实现原理。)
功能模块后也提供了一个完整的主函数测试,仅供学习参考。开头和结尾提供了顺序表的基本原理和使用场景的介绍。该博客中出现的图片和部分代码的实现原理参考于几本比较流行的数据结构相关书籍。(参考书籍见章末)
本文旨在提供比较完整的顺序表的功能模块,希望能帮助初学数据结构的读者快速掌握顺序表的代码实现或是理解其原理。谢谢!
目录
一、顺序表的概述
1.顺序表的由来
线性表是n个数据元素的有限序列。注意,数据元素可以是一个数、一个符号、一页书甚至其他更复杂的信息。
当n=0时,称为空表。在非空表中表头元素a1仅有一个直接前驱,中间元素(a2~ai+1)有且仅有一个直接前驱和一个直接后继,表尾元素an仅有一个直接后继。
根据存储方式,线性表分为顺序存储和链式存储。线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。 线性表(a1 ,a2 ,......,an)的顺序存储示意图如下:
2.顺序表的存储
数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的(线性表长度小于数组长度)。
每个数据元素,不管它是整型、实型还是字符型,它都是需要占用一定的存储单元空间的。假设占用的是c个存储单元,则如下图所示。
二、顺序表的基本操作
顺序表的功能模块:
void initSqList(SqList * list, int init_size); /* 初始化顺序表*/
void reInitSqList(SqList* list, int incre_size); /* 扩展顺序表容量 */
bool isEmptySqList(SqList* list); /* 判断顺序表是否为空表 */
bool isFullSqList(SqList* list); /* 判断顺序表是否为满表 */
bool insertSqList(SqList* list, int local, data_t my_data); /* 将元素插入到指定位置 */
bool deleteSqList(SqList* list, int local); /* 删除指定位置的元素 */
void displaySqList(SqList* list); /* 遍历顺序表 */
bool selectSqList(SqList* list, int local, data_t* my_data); /* 查询指定位置的元素 */
bool updateSqList(SqList* list, int local, data_t new_data); /* 修改指定位置的元素 */
int lengthSqList(SqList* list); /* 统计顺序表数据元素个数 */
void clearSqList(SqList* list); /* 清空顺序表 */
void destroySqList(SqList** list); /* 释放顺序表空间 */
1)包含头文件 顺序表
// 包含所需的C标准库头文件
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
2)构造数据类型 顺序表
#define INIT_SIZE 10 /* 初始化数组容量大小 */
#define INCRE_SIZE 5 /* 扩容增量 */
typedef int data_t;
typedef struct {
data_t* data; /* 存储数据元素的动态数组 */
int length; /* 当前数据元素的个数 */
int list_size; /* 数组容量 */
} SqList;
3)初始化 顺序表
/* 初始化顺序表 */
bool initSqList(SqList* list, int init_size) {
list->data = (data_t*)malloc(sizeof(data_t) * init_size); // 分配初始内存空间
if (NULL==list->data) { //分配失败返回false
return false;
}
list->length = 0; // 初始化数据元素数量为0
list->list_size = init_size; // 设置数组容量
return true; //成功返回true
}
4)扩容 顺序表
/* 扩展顺序表容量 */
bool reInitSqList(SqList* list, int incre_size) {
list->data = (data_t*)realloc(list->data, sizeof(data_t) * (list->list_size + incre_size)); // 重新分配内存空间
if (NULL == list->data) { //realloc重分配内存失败,返回false
return false;
}
list->list_size += incre_size; // 更新表容量大小
return true; //成功,返回true
}
5)判空 顺序表
/* 判断顺序表是否为空表 */
bool isEmptySqList(SqList* list) {
return list->length == 0;// 如果数据元素数量为0,返回true
}
6)判满 顺序表
/* 判断顺序表是否为满表 */
bool isFullSqList(SqList* list) {
return list->length == list->list_size;// 如果数据元素数量等于数组容量,返回true
}
7)插入元素指定位置 顺序表
/* 将元素插入到指定位置 */
bool insertSqList(SqList* list, int local, data_t my_data) { //local为位置(从1开始的),my_data为待插入数据
/* 数组容量不够,扩展顺序表 */
if (isFullSqList(list)) {
if (!reInitSqList(list, INCRE_SIZE)) { //如果扩容失败,返回false
return false;
}
}
/* 插入位置非法 */
if (local < 1 || local > list->length+1) { //插入合法位置在[1,list->length+1]范围内,注意元素的最后一个位置之后(local+1)也是可以插入的
printf("插入位置非法,请输入正确的插入位置!\n");
return false;
}
/* 插入元素 */
for (int i = list->length - 1; i >= local - 1; i--) { //从插入位置local开始的数据全部后移一位,给新数据my_data空出一个位置
list->data[i + 1] = list->data[i];
}
list->data[local - 1] = my_data; //空出的local位置直接赋值即可
list->length++; //更新顺序表的数据元素个数
printf("元素 %d 插入成功!\n", my_data);
return true;
}
8)删除指定位置的元素 顺序表
/* 删除指定位置的元素 */
bool deleteSqList(SqList* list, int local) {
/* 删除位置非法 */
if (local < 1 || local > list->length) { //删除的合法位置在[1,list->length]范围内
printf("删除位置非法,请输入正确的删除位置!\n");
return false;
}
/* 删除元素 */
for (int i = local; i < list->length; i++) { //local位置之后的所有元素向前移动一位
list->data[i - 1] = list->data[i];
}
list->length--; //注意更新顺序表元素个数
printf("删除成功!\n");
return true;
}
9)遍历 顺序表
/* 遍历顺序表 */
void displaySqList(SqList* list) {
if (isEmptySqList(list)) { //顺序表为空,不用遍历
printf("当前线性表为空表\n");
}
else { //顺序表不为空,循环遍历
printf("线性表中元素为:\n[");
for (int i = 0; i < list->length - 1; i++) {
printf("%d, ", list->data[i]);
}
printf("%d]\n", list->data[list->length - 1]);//这里输出最后一个元素
}
}
10)查询指定位置的元素 顺序表
/* 查询指定位置的元素 */
bool selectSqList(SqList* list, int local, data_t* my_data) {
/* 查询位置非法 */
if (local < 1 || local > list->length) { //查询的合法位置在[1,list->length]范围内
printf("查询位置非法,请输入正确的查询位置!\n");
return false;
}
*my_data = list->data[local - 1]; //位置合法,直接获取数据,赋值给my_data
printf("第 %d 个位置上的元素为 %d\n", local, *my_data);
return true;
}
11)修改指定位置的元素 顺序表
/* 修改指定位置的元素 */
bool updateSqList(SqList* list, int local, data_t new_data) {
/* 修改位置非法 */
if (local < 1 || local > list->length) { //修改的合法位置在[1,list->length]范围内
printf("修改位置非法,请输入正确的修改位置!\n");
return false;
}
list->data[local - 1] = new_data;//位置合法,直接修改即可
printf("修改成功!\n");
return true;
}
12)统计数据元素个数 顺序表
/* 统计顺序表数据元素个数 */
int lengthSqList(SqList* list) {
return list->length; //length为顺序表的数据元素个数
}
13)清空 顺序表
* 清空顺序表 */
void clearSqList(SqList* list) {
list->length = 0; //顺序表的清空只需要设置数据元素为0即可
}
14)释放 顺序表
/* 释放顺序表空间 */
void destroySqList(SqList** list) { //注意这里传入的是二级指针,若传入一级指针会造成内存泄露
free((*list)->data); // 释放数据内存空间
free(*list); // 释放结构体内存空间
*list = NULL; // 将括号内部原来的联通参数置为空!
printf("顺序表空间已成功释放!\n");
}
测试代码: (仅作参考)
int main() {
SqList* list;
list = (SqList*)malloc(sizeof(SqList));
initSqList(list, INIT_SIZE);
for (int i = 1; i <= 10; i++) {
insertSqList(list, i, i * 10);
}
displaySqList(list);
insertSqList(list, 4, 55);
insertSqList(list, 7, 77);
displaySqList(list);
deleteSqList(list, 4);
deleteSqList(list, 7);
displaySqList(list);
int data;
selectSqList(list, 3, &data);
updateSqList(list, 3, 100);
displaySqList(list);
clearSqList(list);
displaySqList(list);
destroySqList(&list);
return 0;
}
总结:
顺序表的优点:无须为表中元素之间的逻辑关系而增加额外的存储空间;可以快速的存取表中任一位置的元素。顺序表的缺点:插入和删除操作需要移动大量元素;当线性表长度较大时,难以确定存储空间的容量;造成存储空间的“碎片”。
顺序表适合在需要频繁按照下标访问、频繁进行查找和排序操作、数据元素个数固定且占用空间较小的情况下使用。但是,数据元素个数较大、需要频繁进行插入和删除操作、数据元素占用的空间较大时,考虑使用其他数据结构。
参考资料:
- 程杰:《大话数据结构》
- 王晓华:《算法的乐趣》
- 严蔚敏、吴伟民:《数据结构(C语言版)》
- 渡部有隆:《挑战程序设计竞赛2》
- 石田保辉:《我的第一本算法书》