1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存 储。在数组上完成数据的增删查改。 顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素。
2. 动态顺序表:使用动态开辟的数组存储。
2.2 接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空 间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间 大小,所以下面我们实现动态顺序表。
首先我们要做的就是对顺序表初始化以及销毁,这一步最好连着做,因为这样可以防止自己忘记释放
void SLInit(Seqlist* psl)
{
psl->a =NULL;
psl->capacity = 0;
psl->sz = 0;
}
void SLdestory(Seqlist* psl)
{
if (psl->a != NULL)
{
free(psl->a);
psl->a = NULL;
psl->capacity = 0;
psl->sz = 0;
}
}
在插入前我们都要检查增容问题,所以我们可以独立写一个函数,让每个功能独立运行
void checkcapacity(Seqlist* psl)//检查增容
{
assert(psl);
if (psl->sz == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 4 : 2 * psl->capacity;
SLDatatype* tmp = (SLDatatype*)realloc(psl->a, newcapacity * sizeof(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail");//防止扩容失败
return;
}
else
{
psl->a = tmp;
psl->capacity = newcapacity;
}
}
}
下面就是我们的下面就是头插头删,尾插尾删了
void SLpophead(Seqlist* psl)//头删
{
assert(psl);
checkcapacity(psl);
int begin = 0;
while (begin < psl->sz - 1)
{
psl->a[begin] = psl->a[begin + 1];
++begin;
}
psl->sz--;
}
void SLpushhead(Seqlist* psl, SLDatatype x)//头插
{
assert(psl);
checkcapacity(psl);
int end = psl->sz-1;
while (end--)
{
psl->a[end+1] = psl->a[end];
}
psl->a[0] = x;
psl->sz++;
}
void SLpushback(Seqlist* psl, SLDatatype x)//尾插
{
assert(psl);
checkcapacity(psl);
psl->a[psl->sz] = x;
psl->sz++;
}
void SLpopback(Seqlist* psl)//尾删
{
assert(psl);
assert(psl->sz > 0);
psl->sz--;
}
在这之后就可以打印出方便观察,也就有了打印的函数
void SLPrint(Seqlist* psl)
{
int i = 0;
for (i = 0; i < psl->sz; i++)
{
printf("%d ", psl->a[i]);
}
}
简单的头插头删尾插尾删显然不满足我们处理数据时的要求
下面我们就进行任意位置的删除和插入
void SLInsert(Seqlist* psl,size_t pos, SLDatatype x)//任意位置插入
{
assert(psl);
assert(pos <= psl->sz);
checkcapacity(psl);
size_t end = psl->sz ;
while (end >pos)
{
psl->a[end] = psl->a[end-1];
end--;
}
psl->a[pos] = x;
psl->sz++;
}
void SLErase(Seqlist* psl, size_t pos)//任意位置删除
{
assert(psl);
assert(pos < psl->sz);
size_t begin = pos;
while (begin < psl->sz-1)
{
psl->a[begin] = psl->a[begin + 1];
++begin;
}
psl->sz--;
}
那头插头删尾插尾删的代码就可以修改了,只要调用任意位置删除,插入的函数就可以修改了,
那单独搞一个头插头删尾插尾删的函数有什么意义呢?
其实这也是有意义的,因为在处理数据的时候很多时候我们会选择头插头删尾插尾删。所以独立封装一个函数更好。