在学习顺序表之前,我们需要先了解一下什么是线性表。
线性表(linearlist)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
物理结构与逻辑结构:所谓物理结构,就是数据实际在计算机中存储的结构,与物理结构相对的逻辑结构,其实就是我们人为想象和模拟的数据存储构。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。
这样就是一个简单的顺序表,那问题来了,这不就是一个数组吗,这么看上去顺序表和数组有什么区别啊?这区别嘛,要说大也不大,但也不小:
打个比方吧,今天你想吃一个炒粉丝,那么炒粉丝这道菜在路边的小店能不能吃到?能,在高级餐厅呢?那更可以了,只是高级餐厅会把他改个名字叫什么:蚂蚁上树,这样一听是不是就高级多了,数组和顺序表大概就是这样的关系,数组在这里对应的大概就是炒粉丝,顺序表就是蚂蚁上树
所以顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口,关系有一张图可以帮助大家理解:
简单来说,顺序表就是对数组的封装。
顺序表分为两类:静态顺序表和动态顺序表,实际在使用的时候,用的更多的是动态顺序表,为什么呢?我们一起来了解一下吧。
定义:使用定长数组存储元素的顺序表
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr[N];
int size;
}SL;
上述代码就实现了这样一个大小为定长7的顺序表。但是我们不难发现,静态顺序表这个定长的缺陷:给少了不够用,给多了又浪费。
但是这时候有同学可能会说:前面不是定义了一个宏N为7吗,那我想让数组变大变小直接改N也可以实现啊,其实这个乍一听没什么毛病,甚至觉得有点道理,但是想想看,今天你开辟了100个字节的空间,那将来要存101个呢?你今天开辟了一个1000个字节的空间,那将来要存1001个呢?所以这里其实用静态顺序表并不合适,有人可能会说,现在科技这么发达,内存空间越来越大,我浪费一点空间也没关系吧,这样的说法是很不负责任的,如果所有人都这么想,那将来的程序只会越来越臃肿。所以静态顺序表在操作时并不是很方便,因此我们可以去尝试一下动态顺序表。
与静态顺序表相对,顾名思义,动态顺序表应该是可以灵活调整数组空间大小的
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int capacity;//空间容量
int size;//有效数据个数
}SL;
关于typedef int SLDataType:因为在对结构体进行操作的时候我们也许要放 int char double 等等类型的数据,对它进行typedef方便后续操作
typedef struct SeqList 成 SL 是因为struct SeqList写起来太麻烦了,SL比较简洁。
看过博主动态内存管理的同学应该明白,对于动态顺序表,我们可以对他进行增容操作,运用realloc函数去合理的调整顺序表的大小,灵活的申请空间。
对于顺序表的操作,博主这里会给出 尾插/删、头插/删、任意位置插入/删除、顺序表销毁这7个操作,加深同学们对顺序表的理解。
首先来创建一个顺序表(这里以动态顺序表为例)
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;//因为在对结构体进行操作的时候我们也许要放
//int char double等等类型的数据,对它进行typedef方便后续操作
int capacity;//空间容量
int size;//有效数据个数
}SL;//这里用typedef是因为struct SeqList太长了,简写成SL更方便
上述代码就是来创建一个动态顺序表,接着对他初始化
//初始化SL
void InitSeqList(SL* s)
{
s->arr = NULL;
s->capacity = 0;
s->size = 0;
}
这里将数组置为NULL,空间容量和有效数据都置为0;下面将打印顺序表的函数也准备好。
//打印SL
void Print(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
现在就可以正式对顺序表进行一些实际性操作了。
尾插:顾名思义就是向顺序表末尾插入数据,那么既然是插入一个数据,我们就要检查空间容量,判断顺序表空间是否足够,检查完后,我们就可以向内存申请空间,由于我们之前已经对顺序表进行了初始化,所以我们这里可以直接用realloc函数调整顺序表大小,话不多说,直接上代码:
//检查SL容量
void SLCheckCapacity(SL* ps)
{
//判断顺序表空间是否足够
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
//newcapacity需要乘以数据类型
if (tmp == NULL)//这里是判断是否为空
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);//增加assert断言,增强代码的健壮性
//判断空间是否足够
SLCheckCapacity(ps);
//空间足够的情况下
ps->arr[ps->size++] = x;
}
尾删这里其实可以想一想,只要不打印最后一个元素即可,那代码就会很简单了。
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
ps->size--;
}
只需要让ps->size--,打印的时候就打印不到最后一个元素,这样就实现了尾删操作。
与尾插相对,头插就是向顺序表头部插入一个元素,那我们就来想一想怎么实现呢?
其实不难想:可以把现在顺序表的所有元素先向后移一位,在把要插入的数据放到数组首元素位置,再让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 SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
与前两个插入的函数不同,这个函数会多一个参数,可想而知,这个多的参数就是position,即要向哪里插入的位置。其实可以看成与头插差不多的情形,把要插入位置的元素看做之前头插的第一个元素,再对之后的元素进行上面写的头插操作就行了。
//任意位置的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= pos; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[pos] = x;
ps->size++;
}
这里再次强调,只要涉及到插入元素,就要考虑空间容量是否足够的情况!!
思路和头删的思路差不多,有了刚刚任意位置的铺垫,这里应该更简单了,所以博主这里就不过多赘述,大家看看代码应该就能看明白原理
//任意位置的删除
void SLDelet(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
因为我们顺序表的创建是用到了realloc来调整空间大小,所以使用完之后,要将空间释放掉,顺序表的销毁就是来干这个事的。
//顺序表销毁
void DetroySL(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
好了,以上就是本篇博客的所有内容了,觉得博客写的还不错,可以点点赞哦!