前言
以数组为底层代码的顺序表是一种线性表,无论是在物理结构还是逻辑结构上,都是线性的。线性表可分为静态表和动态表,静态表是指给定内存空间的顺序表,它的内存空间是固定的,不能再被修改,因此可能造成两种极端,空间不足或空间浪费。相比之下,动态表明显优于静态表,它的内存空间不是固定的,是可以随时增大的,因此我们在此只讨论动态表。
1,顺序表的基本元素
能够存储简单数据的顺序表需要满足基本的增删查改的功能,利用循环,数组,结构体,指针,动态内存管理等基本知识来实现。
2,顺序表的实现
建立头文件及两个源文件:头文件用于结构体及函数的声明,两个源文件分别是函数实现代码和测试代码。
1)头文件实现
建立头文件,命名为 SqList.h
在头文件中引用所有需要的库,后续源文件引用头文件即可。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
要想数组实现动态的增删功能,就需要两个变量来记录数组的实际容量和存储的有效数据容量,实际容量可以保证数组不出现越界问题,有效数据容量可以来记录最后一个有效数据的位置来保证数组实时的增删功能。因此我们可以利用结构体来进行封装:
typedef int SLDataType;//存储的数据类型可以进行自定义,这里以简单的整形为例
typedef struct SqList
{
SLDataType* arr;//数组地址
int capacity;//数组实际容量
int size;//数组有效数据容量
}SL;//对结构体进行重命名,减少代码量
接下来对实现顺序表的几个基本功能函数进行声明即可:
void SLPrint(SL s);//用于测试时顺序表的可视化
void SLInit(SL* ps);//初始化顺序表
void SLDestroy(SL* ps);//销毁顺序表
void SLCheckCapacity(SL* ps);//给顺序表申请内存或扩容
void SLPushBack(SL* ps, SLDataType x);//将数据尾插进数组
void SLPushFront(SL* ps, SLDataType x);//将数据头插进数组
void SLPopBack(SL* ps);//尾删
void SLPopFront(SL* ps);//头删
void SLInsert(SL* ps, int pos, SLDataType x);//指定位置之前插入
void SLErase(SL* ps, int pos);//指定位置删除
int SLFind(SL s, SLDataType x);//查找指定数据
在源文件代的实现中会详细解释各个功能函数。
2)源文件实现
原文件分为函数文件(SqList.c)和测试文件(test.c)。
先引用头文件
#include"SqList.h"
首先要对创建的顺序表进行初始化,赋予初始数值。
SLInit函数和SLDestroy函数
函数文件(SqList.c):
顺序表初始时没有任何数据,数组没有分配空间,所以数组地址应为空,实际容量和有效数据容量为0:
void SLInit(SL* ps) //传入的是结构体的地址,只有传址才能修改指定地址结构体
{
ps->arr = NULL;//数组地址
ps->capacity = 0;//实际容量
ps->size = 0;//有效数据容量
}
有初始化,还得有销毁,虽然顺序表的程序运行时占用不了内存,但要保持良好的习惯对内存进行释放。
void SLDestroy(SL* ps) {
if (ps->arr) {
free(ps->arr);//找到数组地址并释放该空间
}
ps->capacity = 0;//
ps->size = 0;//将容量设为0
}
测试文件(test.c):
现在可以对初始化进行测试:
void test01(SL* ps) {
SLInit(ps);//初始化
SLDestroy(ps);//销毁
}
int main() {
SL s;
test01(&s);
return 0;
}
测试结果可以调试时通过监视窗口查看:
执行初始化前:
可以看到结构体成员均为随机值
执行初始化后:
结构体成员发生改变
SLPushBack函数和SLCheckCapacity函数
函数文件(SqList.c):
尾插函数(SLPushBack)即对数组进行插入操作,插入位置为数组的尾部。
要想插入函数即增的功能,就要利用实际容量Capacity和有效数据容量Size两个变量协助,例如:当要插入一个整形“99”时,那么实际容量+1,size由0变为1,则代表数组存储了一个数据,而size在增加的过程中则不能超过Capacity,否则出现越界。如下图,给定Capacity为16个字节即4个整形空间,尾插入99的示意图。
那么若是想再尾插一个数据只需将该数据放入数组的size位置,然后再将size++。
但是在此之前,需要变量Capacity不为0,此时便需要动态申请内存空间,即实现SLCheckCapacity函数,代码如下:
void SLCheckCapacity(SL* ps) {
assert(ps);//首先判定一下结构体地址是否存在
if (ps->size == ps->capacity)//然后要看看size是否和Capacity相等,相等代表空间不足
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//判断实际容量Capacity是否为0,若为零给4个空间,不为0(代表空间不足),则扩大两倍(扩大两倍最为合理)
SLDataType* tmp = (SLDataType*)realloc(ps->arr,sizeof(SLDataType)*newCapacity);
//再利用realloc函数进行空间的申请或扩大
if (tmp == NULL) {
perror("realloc fail!!");
}//利用中间变量防止内存申请失败
ps->arr = tmp;//申请成功后再赋给arr
ps->capacity = newCapacity;//实际容量更新
}
}
然后在SLPushBack函数中引用SLCheckCapacity函数来制造容量并尾插数据:
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
SLPrint函数
函数文件(SqList.c):
该函数用于测试的可视化,即对数组中的数据进行打印,这样便可通过终端查看顺序表执行结果:
void SLPrint(SL s) {
for (int i = 0; i < s.size; i++) {
printf("%d ", s.arr[i]);
}
if (s.size == 0) {
printf("NULL");
}
printf("\n");
}
测试文件(test.c):
此时便可以利用SLPrint函数对SLPushBack函数和SLCheckCapacity函数进行测试:
void test02() {
SL s;
SLInit(&s);
SLPushBack(&s, 99);
SLPrint(s);
SLPushBack(&s, 99);
SLPrint(s);
SLPushBack(&s, 99);
SLPrint(s);
SLPushBack(&s, 99);
SLPrint(s);
SLPushBack(&s, 99);
SLPrint(s);
SLDestroy(&s);
}
int main() {
test02();
return 0;
}
运行结果:
SLPushFront函数
函数文件(SqList.c):
同样是要插入元素的函数,也要先进行SLCheckCapacity函数的检查。但头插与尾插不同,头插即在ps->arr[0]位置放入新元素,需要对数组中所有元素进行移动,运用for循环即可解决问题。
void SLPushFront(SL* ps, SLDataType x) {
assert(ps);//防止传入结构体地址为空
SLCheckCapacity(ps);//插入前检查内存是否充足进而申请内存
for (int i = ps->size; i > 0; i--) {
ps->arr[i] = ps->arr[i - 1];
}//运用for循环从数组末端开始循环,依次后移,最终空出arr[0]的位置
ps->arr[0] = x;//将要插入的值赋给arr[0]
ps->size++;//插入一个元素,有效数据容量+1
}
测试文件(test.c):
对SLPushFront函数进行测试:
void test03() {
SL s;
SLInit(&s);
SLPushBack(&s, 66);
SLPushBack(&s, 66);
SLPrint(s);
SLPushFront(&s, 99);
SLPushFront(&s, 99);
SLPushFront(&s, 99);
SLPushFront(&s, 99);
SLPrint(s);
SLDestroy(&s);
}
int main() {
test03();
return 0;
}
测试结果:
SLPopBack函数和SLPopFront函数
函数文件(SqList.c):
有增就有删,SLPopBack函数和SLPopFront函数分别是尾删和头删,即从尾部删除数据和从头部删除数据。对顺序表中的元素进行删除实际上是对数组中有效数据容量size的更改,而实际容量并不发生改变,因此数组删除实际上就是对size进行“--”操作(size--),但两者具体上也有些不同。
尾删:
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size);//这里要增加一个对size的断言,防止对空的顺序表进行删除操作
ps->size--;//简单的“--”操作即可以实现删除
}
头删:
头删和尾删有些许不同,即删除一个元素不能单纯的靠“size--”来操作,而是要利用for循环使ps->arr[0]后的元素整体前移一位,然后对arr[0]处的元素进行覆盖,再”size--“即可。
void SLPopFront(SL* ps) {
assert(ps);
assert(ps->size);
for (int i = 1; i < ps->size; i++) {
ps->arr[i-1] = ps->arr[i];
}
ps->size--;
}
测试文件(test.c):
对SLPopBack函数和SLPopFront函数进行测试:
void test04() {
SL s;
SLInit(&s);
SLPushFront(&s, 100);
SLPushFront(&s, 101);
SLPushFront(&s, 102);
SLPushFront(&s, 103);
SLPrint(s);
SLPopBack(&s);
SLPrint(s);
SLPopFront(&s);
SLPrint(s);
SLDestroy(&s);
}
int main() {
test04();
return 0;
}
运行结果:
SLInsert函数
函数文件(SqList.c):
SLInsert函数表示指定位置之前插入,相比较与头插和尾插,SLInsert函数的传参多了一个pos参数,传入的是数组下标位置。SLInsert函数和头插类似任何要利用for循环来移动数组中的元素,一个长度为n的顺序表,在i(i>=1&&i<=n+1)位置前插入数据需要移动i-1个元素。
void SLInsert(SL* ps, int pos, SLDataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);//对pos进行限定,保证只在有效数据范围内操作
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >=pos ; i--) {
ps->arr[i + 1] = ps->arr[i];
}//pos位置元素及其后面元素整体后移(pos变为pos+1),pos位置处即为目标位
ps->arr[pos] = x;//给目标位赋值
ps->size++;//有效数据容量+1
}
测试文件(test.c):
对SLInsert函数进行测试:
void test05() {
SL s;
SLInit(&s);
SLPushFront(&s, 100);
SLPushFront(&s, 101);
SLPushFront(&s, 102);
SLPushFront(&s, 103);
SLPrint(s);
SLInsert(&s, 1, 99);
SLPrint(s);
SLInsert(&s, 1, 88);
SLPrint(s);
SLDestroy(&s);
}
int main() {
test05();
return 0;
}
测试结果:
SLErase函数
函数文件(SqList.c):
SLErase函数的作用是删除指定位置数据,即删除pos位置的数据,与头删类似,利用for循环使pos位置往后的数据整体前移然后覆盖pos位置的数据从而达到删除pos位数据的目的,最后再进行“size--”。
void SLErase(SL* ps, int pos) {
assert(ps);
assert(ps->size);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size-1; i++) {
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
测试文件(test.c):
对SLInsert函数进行测试:
void test06() {
SL s;
SLInit(&s);
SLPushFront(&s, 100);
SLPushFront(&s, 101);
SLPushFront(&s, 102);
SLPushFront(&s, 103);
SLPrint(s);
SLErase(&s, 1);
SLPrint(s);
SLErase(&s, 1);
SLPrint(s);
SLDestroy(&s);
}
int main() {
test06();
return 0;
}
测试结果:
SLFind函数
函数文件(SqList.c):
查找函数,顾名思义,是根据所给数据对顺序表进行检索,找出其数据对等的元素。
int SLFind(SL s, SLDataType x) //函数返回值为int,即返回查找到的元素的数组下标,注意这里可以不传地址
{
for (int i = 0; i < s.size; i++) {
if (s.arr[i] == x) {
return i;//利用for循环,符合的直接返回下标
}
}
return -1;//不符合的返回-1(下标不可能为负值)
}
测试文件(test.c):
对SLFind函数进行测试:
void test07() {
SL s;
SLInit(&s);
SLPushFront(&s, 100);
SLPushFront(&s, 101);
SLPushFront(&s, 102);
SLPushFront(&s, 103);
int ret1 = SLFind(s, 102);
if (ret1 > 0) {
printf("找到了,下标为 %d\n", ret1);
}
else
{
printf("没找到\n");
}
int ret2 = SLFind(s, 99);
if (ret2 > 0) {
printf("找到了,下标为 %d\n", ret2);
}
else
{
printf("没找到\n");
}
SLDestroy(&s);
}
int main() {
test07();
return 0;
}
测试结果:
3,源码
//SqList.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDataType;
typedef struct SqList
{
SLDataType* arr;
int capacity;
int size;
}SL;
void SLPrint(SL s);
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLCheckCapacity(SL* ps);
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL s, SLDataType x);
//SqList.c
#include"SqList.h"
void SLInit(SL* ps) {
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
void SLDestroy(SL* ps) {
if (ps->arr) {
free(ps->arr);
}
ps->capacity = 0;
ps->size = 0;
}
void SLCheckCapacity(SL* ps) {
assert(ps);
if (ps->size == ps->capacity) {
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * newCapacity);
if (tmp == NULL) {
perror("realloc fail!!");
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
void SLPrint(SL s) {
for (int i = 0; i < s.size; i++) {
printf("%d ", s.arr[i]);
}
if (s.size == 0) {
printf("NULL");
}
printf("\n");
}
void SLPushFront(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--) {
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size);
ps->size--;
}
void SLPopFront(SL* ps) {
assert(ps);
assert(ps->size);
for (int i = 1; i < ps->size; i++) {
ps->arr[i-1] = ps->arr[i];
}
ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >=pos ; i--) {
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps, int pos) {
assert(ps);
assert(ps->size);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size-1; i++) {
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
int SLFind(SL s, SLDataType x) {
for (int i = 0; i < s.size; i++) {
if (s.arr[i] == x) {
return i;
}
}
return -1;
}
//test.c
#include"SqList.h"
void test01() {
//....
}
//...
int main() {
test01();
//.....
return 0;
}