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