本文将对顺序表概念进行一定的介绍,并实现动态顺序表中一些常用的接口。
目录
一、线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组 上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素。
2. 动态顺序表:使用动态开辟的数组存储。
三、接口及实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
typedef int SLDataType;
// 顺序表的动态存储
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t size; // 有效数据个数
size_t capacity; // 容量空间的大小
}SeqList;
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表修改
void SeqListModify(SeqList* psl, int pos, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
我们分模块来实现上述接口:
① SeqList.h 文件:存放头文件的包含、顺序表结构体和函数的声明。
② SeqList.c 文件:存放函数的定义,来实现顺序表的各个功能。
③ test.c 文件: 用于顺序表的测试。
1.初始化
创建一个顺序表后,需要先对其初始化:
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->array = NULL;//顺序表为空
psl->size = psl->capacity = 0;
//当前元素数量和容量 置为 0
}
2.扩容
写入数据时,先判断顺序表当前的容量能否再次写入数据,不能则需要进行扩容:
void CheckCapacity(SeqList* psl)
{
assert(psl);
//检测扩容
if (psl->size == psl->capacity)//当容量和大小相等时,扩容
{
int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
//容量为0时,开辟的容量为4,其他情况开辟原来容量的2倍
SLDataType* tmp = (SLDataType*)realloc(psl->array, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)//判断是否开辟成功
{
perror("realloc");
exit(-1);
}
psl->array = tmp;
psl->capacity = newCapacity;
}
}
3.数据插入
数据的插入根据上述的接口,有:头部插入数据,尾部插入数据,在指定位置pos插入数据。
此时,我们只需要实现指定位置的数据插入即可,头插和尾插函数只需要将指定的位置设置为顺序表的头部和尾部,再复用函数即可。
在指定位置pos插入数据:
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos <= psl->size);//确保数据正确
//检测扩容
CheckCapacity(psl);
int end = psl->size - 1;//最后元素的下标
//将要插入位置之后的元素 依次后移
//从后向前移动 避免数据的覆盖
while (end >= pos)
{
psl ->array[end + 1] = psl->array[end];
--end;
}
//数据写入
psl->array[pos] = x;
psl->size++;
}
头插:
void SeqListPushFront(SeqList* psl, SLDataType x)
{
//指定位置为0 ,即首元素的下标
SeqListInsert(psl, 0, x);
}
尾插:
void SeqListPushBack(SeqList* psl, SLDataType x)
{
//psl->size 为新插入元素的下标
SeqListInsert(psl, psl->size, x);
}
4.数据删除
数据删除函数同理,只需要完成指定位置删除函数即可。头部和尾部的删除对该函数进行复用即可。
指定位置删除:
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos >= 0 && pos < psl->size);
int begin = pos;//要删除数据的位置
// 将要删除数据位置后方数据向前移动覆盖即可
while (begin < psl->size - 1)
{
psl->array[begin] = psl->array[begin + 1];
++begin;
}
psl->size--;
}
头删:
void SeqListPopFront(SeqList* psl)
{
SeqListErase(psl, 0);
}
尾删:
void SeqListPopBack(SeqList* psl)
{
//psl->size-1 为最后一个数据的下标
SeqListErase(psl, psl->size-1);
}
5.数据的查找
输入需要查找的数据,遍历顺序表,找到数据返回数据的下标,没有找到则返回-1。
int SeqListFind(SeqList* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->array[i] == x)
{
int pos = i;
return pos;
}
}
return -1;
}
6.数据修改
数据的修改只需要根据已知或是通过查找返回的下标直接进行修改即可。
void SeqListModify(SeqList* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos < psl->size);
psl->array[pos] = x;
}
7.数据的打印
遍历数组打印即可。
void SeqListPrint(SeqList* psl)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
printf("%d ", psl ->array[i]);
}
printf("\n");
}
8.顺序表的销毁
释放掉开辟的空间,并将指向动态开辟空间的指针置空。
void SeqListDestory(SeqList* psl)
{
assert(psl);
free(psl ->array);
psl->array = NULL;
psl->capacity = psl->size = 0;
}