P.S.由于本期是一个工程项目的实现,所以我们分了SeqList.h文件、SeqList.c文件和test.c文件。
这是我的代码:
github SeqList
前言:博主今天开始正式进入到数据结构的学习中啦,会继续以不错的更新频率来和大家分享自己的所学所思和所想,希望大家继续多多点赞关注呀!
文章目录
- 顺序表的实现
- 一、背景知识介绍——线性表
- 二、顺序表
- 1.静态顺序表
- 2.动态顺序表
- 三、动态顺序表的实现
- 1.接口函数的实现
- 2.一些思考
顺序表是线性表的一种是n个具有相同特性的数据元素的有限序列。常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
如果在数据的存储结构上也是线性的,那就是顺序表。
如果在数据的存储结构上不是线性的,那就是链表。
顺序表的本质就是数组,依靠一段在物理上连续的空间存储数据,使用数组来完成增删查改。
依靠定长数组来完成,所以也很明显,他的适用范围较小,只适合确定元素个数的数据处理工作。
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
TypeData arr[N];
int size;//有效数据的个数
}Sq;
使用动态开辟的数组空间存储
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* p;
int size;//有效数据个数
int capacity;//容量大小,字节数
}Sq;
为什么这个动态顺序表就需要多设置一个参数呢,大家可以这个角度想,在进行扩容操作时,我们的数据个数没有变,那么size的值不能变,但是其实有效空间已经扩大了,所以我们必须再设置一个量来存储容量大小。
为什么要使用结构体呢,因为我们这些都是在工程中使用,在工程中,我们不能说我扩一次容就设置一个变量来++,我们把该用的数据都放在结构体里,在调用时也很方便清晰。
typedef int SLDataType;//可以在后续更改变量类型的时候,直接在这换int就行
typedef struct SeqList
{
TypeData* p;
int size;
int capacity;
}SL;
注意:不需要更改变量类型的变量不需要用typedef定义,比如size和capacity就不需要。
如果我们需要对这个顺序表进行操作,我们一定要依赖于函数来实现我们的操作,下面就让我们来看看如何实现这些接口的操作。
初始化顺序表
void SLini(SL* psL)//声明
{
psL-> p = NULL;
psL-> size = 0;
psL -> capacity = 0;
}
打印顺序表
void SLprint(SL* psL)
{
for (int i = 0; i < size; i++)
{
printf("%d ", psL->p[i]);
}
printf("\n");
}
销毁顺序表
void SLdestroy(SL* psL)
{
free(psL -> p);
psL -> p = NULL;
psL -> size = 0;
psL -> capacity = 0;
}
检查顺序表空间是否够用
void SLcheck(SL* psL)
{
if ((psL -> size) == (psL -> capacity)
{
int newcapacity = (psL -> capacity) == 0 ? 4 : 2 * psL ->capacity;
SLDataType* p1 = (SLDataType*)realloc(psL->p, sizeof(SLDataType) * nemcapacity);
if (p1 == NULL)
{
perror("realloc");
return;
}
psL->p = p1;
psL->capacity = newcapacity;
}
}
可能在此大家会有疑问,为什么要这么麻烦的初始化,并且初始化后数组空间为0,还要判断呢?
其实这是为了整个工程考虑,初始化数组是因为我们可能根本不知道我们需要处理多少数组,所以设置为0再调整反而是最合理的。其次,判断空间其实不完全是因为初始化后是0,更是因为我们不知道需要处理的数据个数,只有通过这样来判断后,才能安全扩容和安全使用。
后插
void SLpushback(SL* psL, SLDataType x)
{
SLcheck(psL);
psL->p[psL->size] = x;
psL -> size++;
}
前插
void SLpushfront(SL* psL, SLDatatype x)
{
SLCheckCapacity(psl);
// 挪动数据
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
后删
void SLPopBack(SL* psl)
{
// 温柔的检查
/*if (psl->size == 0)
{
return;
}*/
// 暴力检查
assert(psl->size > 0);
psl->size--;//打印的时候打不出来,在后插等操作时还可以给他的值覆盖了。
}
前删
void SLPopFront(SL* psl)
{
assert(psl->size > 0);
int begin = 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
++begin;
}
psl->size--;
//切记,不要写成psL->size - 1;
}
中间某位置插入
void SLinsert(SL* psL, SLDataType x, int pos)
{
SLcheck(psL);
assert((pos >= 0) && (pos <= (psL->size)));
int tmp = (psL->size) - 1;
while (tmp >= pos)
{
psL->p[tmp + 1] = psL->p[tmp];
--tmp;
}
psL->p[pos] = x;
psL->size++;
}
中间某位置删除
void SLerase(SL* psL, int pos)
{
assert((pos >= 0) && (pos <= (psL->size)));
int tmp = pos;
while (tmp < (psL->size) - 1)
{
psL->p[tmp] = psL->p[tmp + 1];
++tmp;
}
psL->size--;
}
查找某元素下标
int SLfind(SL* psL, TypeData x)
{
int i = 0;
for (i = 0; i < psL->size; i++)
{
if (x == psL->p[i])
{
return i;
}
}
return -1;
}
其实刚才的代码也可以继续优化,比如我们在每一个接口里都加上一个assert(psL),可以当我们传入一个空地址时进行报错。
而其实经过这一趟下来大家不难发现,其实顺序表也是有一些弊端的,例如在实现前插、前删、中插和中删时,时间复杂度都是o(n),有点小麻烦。
而且,在扩容的过程中,扩大了浪费,扩小了不够用,很难判断一个度。
所以这就要引出我们接下来要学习的链表了。
并且这个顺序表是依赖数组来实现的,只能存储一种数据类型。
大家敬请期待吧。