C语言:顺序表(上)
1.顺序表的介绍
2.顺序表的实现
1.顺序表的介绍
线性表是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以 数组 和 链式结构 的形式存储。
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
顺序表分为两类:
-
静态顺序表:使用定长数组存储元素。容易出现空间不够用、空间浪费的问题。
-
动态顺序表:空间可以按需申请。
接下来以动态顺序表为例,编程实现顺序表。
2.顺序表的实现
首先,我们打开vs2022,创建一个头文件SeqList.h,用来定义结构体和函数声明,再创建SeqList.c来编写函数,创建test.c文件来进行测试。
实现顺序表的过程中,我们需要realloc函数来进行开辟和调整空间,用assert进行检查,所以我们在头文件SeqList.h中应包含stdio.h、stdlib.h、assert.h。
c
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
定义顺序表SeqList,再用typedef重命名为SL。
c
typedef struct SeqList
{
int* arr;
int size; //有效数据个数
int capacity;//空间大小(个数)
}SL;
指针arr可以指向数组、结构体、字符串等等数据,所以要把int重命名,以便后续更改arr指向的数据。
c
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;//把int重命名,方便后续更改
typedef struct SeqList
{
SLDataType* arr;
int size; //有效数据个数
int capacity;//空间大小(个数)
}SL;
接下来进行函数声明,我们要编写函数实现顺序表的初始化、销毁、打印、尾插/删、头插/删、定位插入/删除、查找。
c
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;//把int重命名,方便后续更改
typedef struct SeqList
{
SLDataType* arr;
int size; //有效数据个数
int capacity;//空间大小(个数)
}SL;
void SLInit(SL* ps);//顺序表初始化
void SLDestroy(SL* ps);//顺序表销毁
void SLPrint(SL s);//顺序表打印
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* ps, SLDataType x);//查找
顺序表初始化:指针ps指向顺序表,把arr先置为NULL,有效数据的个数size为0,空间大小capacity为0。
c
void SLInit(SL* ps)//顺序表初始化
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
顺序表销毁:需要判断顺序表是否为空,若为空则不需要销毁,若不为空则先用free释放arr指向的空间,再把size、capacity置为0。
c
void SLDestroy(SL* ps)//顺序表销毁
{
if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
在创建顺序表之后,我们通过头插、尾插、定位插入的方式往顺序表中插入数据,但在插入之前,我们需要检查arr指向的空间是否足够,不够则用realloc函数增加空间。
我们先判断size与capacity是否相等,若相等,则说明空间不足,如图所示:
size与capacity不相等,则以原空间大小的2倍增加空间。
c
void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大空间
if (t == NULL)//若空间申请失败
{
perror("realloc fail !");
exit(1);//退出程序
}
ps->arr = t;//空间申请成功
ps->capacity = newCapacity;//记录新的空间大小
}
}
尾插:尾插函数需要参数指向顺序表的指针ps和插入的数据x,先用assert检查ps是否为NULL,再通过SLCheckCapacity函数检查空间是否足够,若不足则增加空间,在插入数据之后,还要让size加1,记录新的有效数据个数。
c
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);//检查插入前空间是否足够
ps->arr[ps->size] = x;
ps->size++;
}
头插:头插与尾插类似,头插还需要循环把数据整体向后挪动一位。
c
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++;
}
顺序表打印函数:
c
void SLPrint(SL s)//打印
{
for (int i = 0;i < s.size;i++)
printf("%d ", s.arr[i]);
printf("\n");
}
尾删:只要size减1,让原本最后一个数据无法被访问,就实现了尾删。
c
void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);//顺序表不为空
--ps->size;
}

头删:与尾删类似,头删还要数据整体向前挪一位,且从第二位数据开始挪,通过覆盖第一位数据实现头删。
c
void SLPopFront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
for (int i = 0;i < ps->size - 1;i++)//数据整体往前挪一位
{ //从第二位往前挪,覆盖第一位实现删除
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
定位插入:类似头插、尾插,但多了一个参数pos,pos为数据插入后的下标,所以0<=pos<=size,在插入前,下标为pos及大于pos的数据往后挪一位。
c
void SLInsert(SL* ps, int pos, SLDataType x)//定位插入,插入后下标为pos
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
定位删除:pos为下标,显然0<=pos<size,下标大于pos的数据往前挪一位,通过arr[pos+1]把arr[pos]覆盖掉来实现定位删除。
c
void SLErase(SL* ps, int pos)//定位删除
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
{ //arr[pos+1]覆盖掉arr[pos]
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
查找:SLFind函数的参数x为要查找的数据,遍历整个顺序表,找到返回下标,找不到返回-1。
c
int SLFind(SL* ps, SLDataType x)//查找
{
assert(ps);
for (int i = 0;i < ps->size;i++)
{
if (ps->arr[i] == x)
return i;//找到了,返回下标
}
return -1;//未找到
}
在SeqList.c文件中的代码如下:
c
//SeqList.c
#include"SeqList.h"
void SLInit(SL* ps)//顺序表初始化
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLDestroy(SL* ps)//顺序表销毁
{
if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大空间
if (t == NULL)//若空间申请失败
{
perror("realloc fail !");
exit(1);//退出程序
}
ps->arr = t;//空间申请成功
ps->capacity = newCapacity;
}
}
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);//检查插入前空间是否足够
ps->arr[ps->size] = x;
ps->size++;
}
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 SLPrint(SL s)//打印
{
for (int i = 0;i < s.size;i++)
printf("%d ", s.arr[i]);
printf("\n");
}
void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);//顺序表不为空
--ps->size;
}
void SLPopFront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
for (int i = 0;i < ps->size - 1;i++)//数据整体往前挪一位
{ //从第二位往前挪,覆盖第一位实现删除
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x)//定位插入,插入后下标为pos
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps, int pos)//定位删除
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
{ //arr[pos+1]覆盖掉arr[pos]
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
int SLFind(SL* ps, SLDataType x)//查找
{
assert(ps);
for (int i = 0;i < ps->size;i++)
{
if (ps->arr[i] == x)
return i;//找到了,返回下标
}
return -1;//未找到
}
最后,我们就可以在test.c文件中做测试了,例如测试头插和尾插:
c
#include"SeqList.h"
void test1()
{
SL s;
SL* ps = &s;
SLInit(ps);
SLPushFront(ps, 1);
SLPushBack(ps,2);
SLPushBack(ps, 3);
SLPushBack(ps, 4);
SLPrint(s);
SLDestroy(ps);
}
int main()
{
test1();
return 0;
}

拙作一篇,望诸位同道不吝斧正。