前言:
在之前我们已经对复杂度进行的相关了解,因此现在我们将直接进入数据结构的顺序表的相关知识的学习。
目录
- 1.线性表
- 2.顺序表
- 2.1概念及结构
- 2.2 接口实现
- 2.2.1.打印顺序表
- 2.2.2初始化顺序表
- 2.2.3.容量的检查
- 2.2.4.销毁顺序表
- 2.2.5.尾插操作
- 2.2.6.尾删操作
- 2.2.7.头插操作
- 2.2.8.头删操作
- 2.2.9指定位置处插入数据
- 2.2.10删除指定位置数据
- 2.2.11begin查找x的起始位置
1.线性表
定义:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储
线性表的顺序存储示意图如下:
2.顺序表
2.1概念及结构
定义:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,顺序表中存放数据的特点和数组这种数据类型完全吻合,所以顺序表的实现使用的是数组。在数组上完成数据的增删查改。
注意事项:
数组实现顺序表的存储结构时,一定要注意预先申请足够大的内存空间,避免因存储空间不足,造成数据溢出,导致不必要的程序错误甚至崩溃
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
SLDataType a[N];
int size; // 记录存储多少个有效数据
}SL;
静态的顺序表(相当于一个数组,数组长度固定的,存储有效个数据)
- 动态顺序表:使用动态开辟的数组存储。
// 动态顺序表 -- 按需扩空间
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size; // 记录存储多少个有效数据
int capacity; // 空间容量大小
}SL;
注意:
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
2.2 接口实现
通常有以下几种接口(接口其实就是实现功能的函数,数据结构以接口称之)
//打印顺序表
void SLPrint(SL* ps);
//初始哈顺序表
void SLInit(SL* ps);
//销毁顺序表
void SLDestroy(SL* ps);
// 尾插尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
// 头插头删
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
// 中间插入删除
// 在pos位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
// 删除pos位置数据
void SLErase(SL* ps, int pos);
//int SLFind(SL* ps, SLDataType x);
// begin查找x的起始位置
int SLFind(SL* ps, SLDataType x, int begin);
接下来我们便开始一一实现相应的功能:
2.2.1.打印顺序表
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
2.2.2初始化顺序表
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
assert函数断言传过来的指针是否为空,若为空就直接结束程序 。
2.2.3.容量的检查
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->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
我们在思考如何在顺序表的尾部插入数据时我们会想到一个问题,如果顺序表满了该怎么办呢?我们可以进行扩容,但是扩大多少呢?扩大的容量过大会导致空间的浪费,扩大的太小会导致频繁申请内存,拖慢程序运行速度
这里采用的是检查容量的方式来实现顺序表的动态存储,(size)是已经存入的数据个数,(capacity)是可以存储数据的个数,当存入和容量相等即空间满了的时候,这里采用realloc函数对顺序表进行扩容。因为realloc函数实在堆区申请空间的所以一次扩容不宜过多这里是一次扩容到原来的两倍。
2.2.4.销毁顺序表
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
}
2.2.5.尾插操作
思想:在开始进行尾插操作时我们需要先对顺序表进行空间检查的操作来判断是否可以直接进行插入操作。
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
2.2.6.尾删操作
思想:尾删操作我们可以直接进行size–操作,size是我们存入数据的大小,
void SLPopBack(SL* ps)
{
//assert(ps);
// 温柔的检查
///*if (ps->size == 0)
//{
//return;
//}*/
// 暴力的检查
assert(ps->size > 0);
ps->a[ps->size - 1] = 0;
ps->size--;
}
2.2.7.头插操作
思想:首先我们还是先对容量进行检查,看线性表的容量是否充足。具体的思想就是每次插入就将第一个数据位置空出来,将每个元素依次向后移动一个位置,我们采用从后向前移动的方法,因为从前往后覆盖的话会导致我们后一个数据被覆盖。
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
// 挪动数据
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
注意:
我们可以看出头插操作我们的时间复杂度为O(n),而尾插操作的时间复杂度为O(1)。因此我们看出两种方法的优劣。
2.2.8.头删操作
思想:还是先检查我们线性表的容量大小,因为删除一个数据我们就需要进行相应的覆盖的操作,具体思路还是将线性表的第二个元素开始后继整体向前移动一个元素,这里是从前向后进行操作,如果从后向前依次挪动,则会造成顺序表中数据被覆盖导致内容未被修改。
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
2.2.9指定位置处插入数据
思想:对指定的位置进行插入操作,如果我们进行插入的位置不符合我们的规范,检查顺序表的空间是否充足才能用来进行插入数据,在指定位置进行插入操作,将我们要插入位置向后进行移动,腾出相应的空间位置在进行插入操作,具体代码如下:
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0);
assert(pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
2.2.10删除指定位置数据
思想:我们进行指定位置删除操作,具体思想就是将指定位置之后的所有元素向前挪移一个元素,将我们要删除元素的位置覆盖掉,采用从后向前覆盖的方式,值得注意的是越界的问题。
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0);
assert(pos < ps->size);
//assert(ps->size > 0);
// 挪动数据覆盖
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
2.2.11begin查找x的起始位置
思想:begin为我们查找的位置,查找到begin位置所对应的下标,即找到我们的所需,如果找不到则返回-1
int SLFind(SL* ps, SLDataType x, int begin)
{
assert(ps);
for (int i = begin; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
总结:以上就是对于线性表的实现操作,大家认真整理,下期我们用相应的题目进行相应的练习加深认识,最后祝大家新年快乐!