线性表
在实际运用中线性表有着很高的实用价值,线性表就是指一种逻辑上是连续的具有相同数据特性的有限序列,一般我们都以数组或者链表的形式去实现。
如图这两种都是线性表,数组无论从内存空间的角度还是逻辑上都是连续的,而链表则只是在逻辑上是连续的,每一个链表的结点都带着指向下一个结点的指针,在内存空间中它可以是分散的也可以是连续。
顺序表
顺序表是线性表的一种,它代表着在内存空间中每个数据都是按顺序存储的。实际上也就是一个数组。既然它就是一个数组那我们是否直接创建一个数组还方便?
数组的创建很容易只需要定义一下就可以了,但是数组的维护就麻烦了,每个数据存储在什么地方我们不清楚,数组需要多大的空间我们也不清楚,数组满了怎么办?为了解决这些问题我们就可以创建一个顺便表,我们可以将顺序表看作是一个功能强大一些的数组,它可以帮我们找到想要的数据,找到数据的位置,并且在数组任何地方插入删除,在数组大小不够的时候我们可以扩大这个数组。
顺序表创建
我们知道了顺序表就是一个功能强大的数组,那么我们就不仅仅只创造数组就可以了,我们需要一个能定义这个强大数组的结构,所以我们需要用到结构体:C语言 结构体-CSDN博客,有了结构体我们就可以很好的创建我们需要的顺序表了。
如图这就是我们接下来要创建的顺序表:我们重命名了一个数据类型为顺序表数据类型(Seqdate),这个是可以随着我们要存储的数据类型变更而变化的,这里我们用的是整形(int)。这里除了我们将要存储的数据的数组指针还有两个变量分别记录了顺序表的数据个数和容量,如果我们还需要其他的变量去描述这个数组也可以自定义去添加,这里我们就实现一个最简单的顺序表。
顺序表的初始化大小
虽然我们在创建时也可以一次性创建一个大大的数组,我们不知到需要存储的数据有多少,需要存储的数据特征是否只有一种,若是直接把空间都用完了后续就非常麻烦,那么我们就直接创建一个可以变长的数组,这样对空间的使用才更合理。
所以我们这里就使用了动态开辟内存空间,这样我们创建的空间是连续的同时还可以变大变小。函数中我们传的是顺序表即结构体的地址。
这里是设置了一个初始大小的顺序表这里表示初始化后我们的这个顺序表是可以存储4个数据的,这个可以按照需求去创建,即使是0也没问题的。接下来malloc返回的是创建好的内存首地址,地址类型是void所以便需要强制类型转换成我们需要存储的数据类型(Seqdate*)的指针并赋给我们结构体内的数组指针,这样方便管理。刚开始没有数据我们就将数据个数置为0,容量便是顺序表一共可以容纳的数据个数为初始化的4。
顺序表的功能实现
我们创建好了顺序表了,但是功能还没完善,接下来我们便需要将这个顺序表一步步完善成我们需要的样子。
顺序表中添加元素
我们有了顺序表不可避免里面肯定需要存储我们需要的数据,所以我们第一个功能肯定就是向顺序表里添加元素。
void Pushback(SL* pl,Seqdate n)//对顺序表进行后插数据
{
if (pl->size == pl->capacity)
{
Seqdate* tmp = (Seqdate*)realloc(pl->p, pl->capacity * sizeof(Seqdate) * 2);
assert(tmp);
pl->p = tmp;
tmp = NULL;
pl->capacity *= 2;
}
pl->p[pl->size] = n;
pl->size++;
}
如上代码,我们进行元素添加时不可避免的一步:先检查顺序表的空间是否足够,若是不够我们自然需要扩大这个顺序表的。这时我们定义的顺序表元素个数和容量就有用了,当元素个数和容量大小相等便是顺序表满了这时我们便使用realloc扩建这个表,当然扩建是有可能失败的(内存空间不足),所以也需要断言一下是否失败。若是当前数组后面的内存不够时这个函数是会另外寻找一块空间开辟并将旧数组中的值都复制到新的地址并释放旧空间的,那我们只需要将realloc返回的地址(无论是否原地址)赋给我们结构体中管理的指针便可以,然后我们自然需要将扩容后的大小再赋给结构体中的容量变量。
因为我们这个代码中实现的是后插即在数组最后插入,并不会影响到前面的数据,所以我们直接赋值给数组中的第(size+1)个数的后面即可,因为数组下标从0开始我们直接如上代码赋值便可。接下来数据个数增加了我们结构体管理的数据个数(size)加一就完成了这次的数据添加了。
前插的代码如下:
void Pushpfront(SL* pl, Seqdate n)//对顺序表进行前插数据
{
if (pl->size == pl->capacity)
{
Seqdate* tmp = (Seqdate*)realloc(pl->p, pl->capacity * sizeof(Seqdate) * 2);
assert(tmp);
pl->p = tmp;
tmp = NULL;
pl->capacity *= 2;
}
for (int count = pl->size; count > 0; count--)
{
pl->p[count] = pl->p[count - 1];
}
pl->p[0] = n;
pl->size++;
}
与后插不一样的是,在前插的过程中我们数组内的数据都需要有序的向后移动一位便如图需要一个循环去解决,这个循环的条件是:从最后一位开始移动直接替代后一位的数便可(所以我们需要从后开始),一直到第一个数即下标为0的数据,移动好之后我们直接将新数据赋值给下标为0的空间便完成了这次赋值了。
顺序表删除元素
当顺序表中有些数据我们已经不需要了,那就不能继续让它占用我们空间了就需要删除功能,与上一样我们先来实现前删和尾删,尾删代码:
void Popback(SL* pl)//顺序后删数据
{
if (pl->size == 0)
{
printf("顺序表为空");
return;
}
pl->size--;
}
判空是必要的当我们顺序表已经没有值了,这时候删除也没有任何意义了,直接返回。当我们的顺序表还有数据的时候,如图我们直接将结构体中记录有效数据个数的变量减一便完成了尾删,因为我们后续的访问数据必然是根据有效数据个数来访问数组后面的数据我们直接当作是随机数,后续添加时可以直接覆盖的便可。若是我们从数据安全角度去考虑给它置为0或者其他数也可以。我们直接在判空后pl->p[size-1]=0;然后将有效数据个数减一。
前删:
void Popfront(SL* pl)//顺序表前删数据
{
if (pl->size == 0)
{
printf("顺序表为空");
return;
}
for (int tmp = 0; tmp < pl->size; tmp++)
{
pl->p[tmp] = pl->p[tmp + 1];
}
pl->size--;
}
前删我们就直接从第二个数开始覆盖前一个直到下标为[size-1]即最后一个数。
顺序表的随机访问
顺序表的本质就是一个数组,所以我们只需要得到这个数据的下标就可以直接访问这个数据,我们可以对这个数据进行修改,也可以对这个数据进行插入和删除。
int Listfind(SL* pl, Seqdate n)//顺序表查找某一数据返回下标,表中无此数据返回-1
{
for (int tmp = 0; tmp < pl->size; tmp++)
{
if (pl->p[tmp] == n)
return tmp;
}
return -1;
}
如上代码,我们将顺序表的地址传和我们需要寻找的数据传给函数,然后足个对比,相同便返回下标数,若是数组中没有相投的便返回-1,这样我们就得到了数据的下标了。
随机插入
如上我们找到了这个数据所在的空间,然后进行插入操作的话如下代码
void SeqlistInsert(SL* pl,int a,Seqdate n)//在下标为a的位置前插即在第a+1个数据的位置插入
{
assert(a <= pl->size && a >= 0);
if(pl->size==pl->capacity)
{
Seqdate* tmp=(Seqdate*)realloc(pl,pl->capacity*2*sizeof(Seqdate));
assert(tmp);
pl->p = tmp;
tmp = NULL;
pl->capacity *= 2;
}
if (a == pl->size)
{
pl->p[a] = n;
pl->size++;
}
else
{
for (int count = pl->size; count > a; count--)
{
pl->p[count] = pl->p[count - 1];
}
pl->p[a] = n;
pl->size++;
}
}
随机插入与前插一样需要对数组的数据进行位移,这里我们是在下标的位置出插入即在目标数据前面插入。所以我们的循环条件是:从最后一个开始将前一个移到后一个位置,直到目标数据的下标,移动完后直接将新数据赋值给此下标。这里我还用了一个条件分支语句判断位置,因为当我们插入位置是最后一个的后面时,只需要直接插入就相当于后插。
随机删除:
void SeqlistDelete(SL* pl, int a)//删除下标为a位置的数据
{
if (pl->size == 0)
{
printf("顺序表为空");
return;
}
for (int tmp = a; tmp < pl->size-1; tmp++)
{
pl->p[tmp] = pl->p[tmp + 1];
}
pl->size--;
}
删除操作与前插差不多,直接覆盖,只是将开始的位置变为从下标为目标数据的下标。
小测
到这里我们基本顺序表的功能都实现完了就小测一下:
初始化:我们的容量是4,有效数据是0,数组内的数都是随机值,malloc在创建空间是并不会初始化空间。
第一个数据的插入:有效数据个数变为1,容量没满不变,数组的一个位置已经赋值为我们添加的数据5.
插入的第二个数:这里我们还是后插,有效数据个数为2,容量不变,数组的第二个位置变为新插入的数据6.
插入第三个数:这里我们是前插,先是将第二个位置的6赋值给第三个位置
然后又将第一个位置得数据赋给了第二的位置
最后将新添加的数赋值给第一个位置完成覆盖并将有效数据个数加一。
满顺序表扩容:
这里我们旧表已经满了创建了一个新表并将值复制过去。
更换新的空间后我们容量已经是扩大了一倍。然后再进行与上一样的头插操作。
随机插入
这里我们先是插入了6个数再通过随机访问找到我们需要寻找的数据(3)返回下标再进行随机插入.
我们需要找数据3,当前的下标为【2】这里返回的tmp为2.
进入随机插入,从最后一个数据开始移动将第6个下标【5】的数开始一个个向后移动与前插一样
当移动完目标下标的数据后将新添加的数据(4)赋值给目标下标的空间完成随机插入。
随机删除:
与插入一样先找到下标,
进入删除函数后直接将目标下标下一个数据覆盖当前数据循环直到最后一个有效数据覆盖完成,size--完成随机删除。