顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用的是数组存储。
顺序表的增删查改实际上是作用在数组上的,而这个数组不是静态的,而是动态内存开辟的。
为什么不使用静态的顺序表?
静态的顺序表存在一个问题,数组开辟的空间是多大,开辟的空间太大的话会造成空间的浪费,
开辟的空间过小的话又有可能不够用。所以使用动态内存开辟的方式来对数组进行扩容更满足我们的使用需求。
2.两个源文件和一个头文件在实现顺序表的增删查改时,采用的是分模块(把各个功能分装成函数)的代码书写方式,这样有利于锻炼我们对代码的掌控力,也能让代码看起来逻辑更加清晰。
一个头文件:SeList.h
头文件在这里的作用是函数的声明、头文件的引用、结构体的声明等内容;
两个源文件:SeList.c test.c
SeList.c 负责的是函数的实现,也就是实现函数功能的接口;
test.c 测试函数功能的正确性,也就是测试我们写的功能是否有bug;
3.增删查改的实现结构体动态内存管理需要创建一个结构体,这个结构体中有数据的个数、容量空间的大小、指向数组的指针。
这三个元素是动态顺序表必不可少的,缺一不可。
typedef struct SeList
{
int size;//顺序表当前元素个数
int capacity;//顺序表当前容量
SLDataType* a;//指向动态数组指针
}SL;
//使用typedef对结构体struct SeList进行重命名,简化代码
有了结构体,那么就需要对它进行初始化:
初始化的过程中首先要避免一个问题,这个问题是我们常见的,在SeInit函数传参的时候不能传结构体,因为传过去的结构体
是实参,而接收它的形参只是实参的一份临时拷贝,实参的变化不会影响实参,所以初始化的时候传的是结构体指针。
SeInit的实现:
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;//顺序表的指针初始化为空,后面使用动态内存开辟空间
ps->capacity = ps->size = 0;//顺序表初始化为空
}
初始化函数完成,之后首先在test.c中创建一个结构体SL sl ; 然后对它进行初始化。
SL sl; SLInit(&sl);
可以进行调试看一下效果:
Checkcapacity(检查是否需要扩容)的实现:realloc函数:
realloc函数有一个注意点,当传给realloc函数的参数ptr为空时,realloc的作用就和malloc函数的功能是一样的,
就是开辟空间,把开辟空间的地址作为返回值。
两种扩容的情况:
1.ps->size == ps->capacity(数据个数和容量相等)
2.ps->capacity == 0(容量为0)
借助一个变量newcapacity进行扩容,如果ps->capacity == 0那么把capacity的值初始化为4,如果不是这种情况,那么
把容量扩为原来容量的二倍,也就是newcapacity = ps->capacity * 2;
void Checkcapacity(SL* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newcapacity = ps->size == 0?4:ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("Checkcapacity::realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
SLInsert(任意位置的插入)的实现:
只要是把数据插入表格中,都需要考虑是否需要增容,所以先考虑是否增容;
任意位置插入,插入位置为pos,pos的大小是由限制的:pos >= 0;
pos <= sz(这里可以=主要是进行尾插),如果pos < sz那么不能进行尾插。
从后往前挪动(从前往后挪动会造成数据的覆盖),定义一个变量end;
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0);
assert(pos <= ps->size);//尾插时为==
Checkcapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
SLPrint的实现:
打印函数,打印顺序表的各个数据,和打印数组的方式相同只需要借助一个循环。
void SLPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("n");
}
SLErase的实现:
任意位置删除数据,和任意位置的插入数据是相似的都需要挪动数据,只不过挪动的方式不同;
直接用后面的数据对前面的数据进行覆盖即可:
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//同时检查了size的数目大于0
//从前往后覆盖
int begin = pos;
while (begin < ps->size - 1)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
完成了任意位置的插入和任意位置的删除,头插/尾插、头删/尾删都是可以复用的:
4.顺序表增删查改源码 SeList.hSLPushFront : SLInsert(ps, 0, x);
SLPushBack : SLInsert(ps, ps->size, x);
SLPopFront : SLErase(ps, 0);
SLPopBack : SLErase(ps, ps->size - 1);
#includeSeList.c#include #include typedef int SLDataType; typedef struct SeList { int size;//顺序表当前元素个数 int capacity;//顺序表当前容量 SLDataType* a;//指向动态数组指针 }SL; void SLInit(SL* ps); void SLPrint(SL* ps); void Checkcapacity(SL* ps); void SLInsert(SL* ps, int pos, SLDataType x); void SLErase(SL* ps, int pos); void SLPopBack(SL* ps); void SLPopFront(SL* ps); void SLPushBack(SL* ps, SLDataType x); void SLPushFront(SL* ps, SLDataType x); int SLFind(SL* ps, SLDataType x); void SLModify(SL* ps, int pos, SLDataType x);
#include "SeqList.h"
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;//顺序表的指针初始化为空,后面使用动态内存开辟空间
ps->capacity = ps->size = 0;
}
void Checkcapacity(SL* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newcapacity = ps->size == 0?4:ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("Checkcapacity::realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0);
assert(pos <= ps->size);//尾插时为==
Checkcapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
void SLPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("n");
}
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//同时检查了size的数目大于0
//从前往后覆盖
int begin = pos;
while (begin < ps->size - 1)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
void SLPopBack(SL* ps)
{
assert(ps);
ps->size--;
//SLErase(ps, ps->size - 1);
}
void SLPopFront(SL* ps)
{
assert(ps);
//从前往后覆盖
int begin = 0;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
//SLErase(ps, 0);
}
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);
ps->a[ps->size] = x;
ps->size++;
//SLInsert(ps, ps->size, x);
}
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);
//从后往前挪动
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
//SLInsert(ps, 0, x);
}
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
void SLModify(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
test.c(这里是我在写程序的时候的一些测试情况,有兴趣的铁汁可以测一下)
int main()
{
SL sl;
SLInit(&sl);
SLInsert(&sl, 0, 1);
SLInsert(&sl, 0, 1);
//SLPushFront(&sl, 10);
SLPushBack(&sl, 200);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPushFront(&sl, 200);
SLPrint(&sl);
return 0;
}



