文章目录
- 线性表
- 线性表的定义
- 线性表分类
- 顺序表
- 顺次表的存储结构
- 实现顺序表的主要接口函数
- 初始化顺序表
- 顺序表尾插
- 顺序表尾删
- 顺序表头插
- 顺序表头删
- 在指定位置插入数据
- 在指定的位置删除数据
- 头插,头删,尾插,尾删新写法
- 打印顺序表
- 销毁顺序表
线性表
线性表的定义
- 线性表是n个具有相同属性的数据元素的有限序列。
- 线性表在逻辑上是线性结构,也就是说连续的一条直线。但是在物理结构上不一定是连续的
- 线性表在物理结构(存储结构)上一般采用顺序和链式的形式存储
线性表分类
顺序表
顺序表是用一段物理地址连续的存储单元存储元素的线性结构,一般采用数组进行存储。在数组上完成数据元素的增删查改
顺序表一般分为:
- 静态顺序表:使用定长的数组存储
- 动态顺序表:使用动态开辟的数组存储
静态顺序表只适合确定需要存储多少数据的场景,如果存储数据量不确定的话,空间开太大浪费,开太小不够用。一般都会去使用动态顺序表,根据情况分配多大的空间。下面将介绍动态顺序表
顺次表的存储结构
图示:
typedef int ElemType;
typedef struct SeqList
{
ElemType* a;
int size;
int capacity;
}SeqList;
定义一个动态顺序表需要三个属性
1.存储空间的地址,需要一段空间来维护顺序表,需要知道顺序表的起始地址
2.顺序表的元素个数,记录顺序表的元素个数,
3.顺序表的空间容量,用来分配空间
实现顺序表的主要接口函数
//顺序表初始化
void SeqListInit(SeqList* ps);
//顺序表尾插
void SeqListPushBack(SeqList* ps, ElemType x);
//检查容量
void CheckCapicity(SeqList* ps);
//顺序表尾删
void SeqListPopBack(SeqList* ps);
//顺序表头插
void SeqListPushFront(SeqList* ps, ElemType x);
//顺序表头删
void SeqListPopFront(SeqList* ps);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, ElemType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
//打印顺序表
void SeqListprintf(SeqList* ps);
//销毁顺序表
void DestroyedSeqList(SeqList* ps);
初始化顺序表
这里先为顺序表申请了2个元素类型的空间大小。
void SeqListInit(SeqList* ps)
{
ps->a = (ElemType*)malloc(sizeof(ElemType)*2);
ps->size = 0;
ps->capacity = 2;
}
顺序表尾插
在尾部插入的时候要考虑两种情况,分别是
- 顺序表未满尾插:直接将元素放入尾部即可
- 顺序表已满的情况下,则需要申请更大的空间来存放数据
代码实现:
void SeqListPushBack(SeqList* ps, ElemType x)
{
assert(ps);
//检查容量
CheckCapicity(ps);
//尾插
ps->a[ps->size] = x;
ps->size++;
}
这里将检查容量封装成一个函数,方便后面插入检查继续复用
void CheckCapicity(SeqList* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity * 2;
ElemType* tmp = (ElemType*)realloc(ps->a,sizeof(ElemType)*newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
顺序表尾删
在尾删时,也应该考虑两种情况,分别是
-顺序表已空时,无需删除
-顺序表未空,直接删除尾部元素即可
代码实现:
这里提供两种写法,一种是暴力检查,程序直接崩溃,一种是防止越界程序可以正常运行
- 暴力检查版
void SeqListPopBack(SeqList* ps)
{
//判空
assert(ps->size > 0);//如果尾删空顺序表,程序直接崩溃
//删除
--(ps->size);
}
- 防止越界版
void SeqListPopBack(SeqList* ps)
{
//判空
if (ps->size == 0)
{
return;
}
//删除
--(ps->size);
}
顺序表头插
和尾插一样,要考虑是否有空间,但是与尾插不同的地方在于,需要挪动数据进行插入
图解
代码实现:
void SeqListPushFront(SeqList* ps, ElemType x)
{
//检查容量
CheckCapicity(ps);
//挪动数据
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
//插入
ps->a[0] = x;
++ps->size;
}
顺序表头删
头删和尾删一样,先判空。与尾删不一样的地方在于删完后需要挪动数据
图解
代码实现:
void SeqListPopFront(SeqList* ps)
{
//判空
if (ps->size == 0)
{
return;
}
//挪动数据覆盖删除
int start = 0;
while (start <= ps->size)
{
ps->a[start] = ps->a[start + 1];
start++;
}
--ps->size;
}
在指定位置插入数据
和头插的思想基本一样
图解:
代码实现:
void SeqListInsert(SeqList* ps, int pos, ElemType x)
{
//检查容量
CheckCapicity(ps);
//挪动数据
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
//插入
ps->a[pos] = x;
++ps->size;
}
在指定的位置删除数据
思想与头删基本一样
图解:
代码实现:
void SeqListErase(SeqList* ps, int pos)
{
//判空
if (ps->size == 0)
{
return;
}
//挪动数据覆盖删除
while (pos <= ps->size)
{
ps->a[pos] = ps->a[pos + 1];
pos++;
}
--ps->size;
}
有了在指定位置插入和删除前提下,头插,头删,尾插,尾删新写法
头插,头删,尾插,尾删新写法
//头插
void SeqListPushFront(SeqList* ps, ElemType x)
{
SeqListInsert(ps, 0, x);
}
//头删
void SeqListPopFront(SeqList* ps)
{
SeqListErase(ps, 0);
}
//尾插
void SeqListPushBack(SeqList* ps, ElemType x)
{
SeqListInsert(ps, ps->size,x);
}
//尾删
void SeqListPopBack(SeqList* ps)
{
SeqListErase(ps, ps->size);
}
打印顺序表
void SeqListprintf(SeqList* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
销毁顺序表
动态开辟的内存需要我们主动去释放空间 这里需要主动free
void DestroyedSeqList(SeqList* ps)
{
free(ps->a);
ps->a == NULL;
ps->capacity = ps->size = 0;
}