一、什么是线性表?
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、什么是顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表一般可以分为: 1.静态顺序表:就是存储空间固定,如果空间被数据放满了,此时就不能再插入数据了
2.动态顺序表:存储空间不固定,可以增容
三、顺序表的实现
1.顺序表的接口
静态顺序表只适用于确定知道需要存多少数据的场景。因为静态顺序表的数组大小是固定的,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
代码如下:
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a;
int capacity;
int size;
}SL;
void SeqListInit(SL* ps);
void SeqListCheckCapacity(SL* ps);
void SeqListPrint(SL* ps);
void SeqListPushBack(SL* ps, SQDataType x);
void SeqListPopBack(SL* ps);
void SeqListPushFront(SL* ps, SQDataType x);
void SeqListPopFront(SL* ps);
void SeqListDestory(SL* ps);
void SeqListInsert(SL* ps, size_t pos, SQDataType x);
void SeqListErase(SL* ps, size_t pos);
int SeqListFind(SL* ps, SQDataType x);
void SeqListModify(SL* ps, size_t pos, SQDataType x);
2.每个接口的实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。 代码如下:
void SeqListPrint(SL* ps)
{
assert(ps && ps->a);
for (int i = 0; i < ps->size; i++)
{
printf("%d ",ps->a[i]);
}
printf("\n");
}
void SeqListDestory(SL* ps)
{
assert(ps && ps->a);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListCheckCapacity(SL* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int NewCapacity = ps->capacity == 0 ? 4 : 2*ps->capacity;
SQDataType* tmp = (SQDataType*)realloc(ps->a, NewCapacity*sizeof(SQDataType));
if (!tmp)
{
printf("relloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = NewCapacity;
}
}
}
void SeqListPushBack(SL* ps,SQDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
ps->a[ps->size++] = x;
}
void SeqListPopBack(SL* ps)
{
assert(ps && ps->a);
assert(ps->size > 0);
ps->size--;
}
void SeqListPushFront(SL* ps, SQDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
void SeqListPopFront(SL* ps)
{
assert(ps && ps->a);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
int SeqListFind(SL* ps, SQDataType x)
{
assert(ps && ps->a);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListInsert(SL* ps, size_t pos, SQDataType x)
{
assert(ps && ps->a);
assert(pos >= 0 && pos <= ps->size);
SeqListCheckCapacity(ps);
size_t end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps, size_t pos)
{
assert(ps && ps->a);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
void SeqListModify(SL* ps, size_t pos, SQDataType x)
{
assert(ps && ps->a);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
下面我们将用画图的方式来分析头插、头删、尾插、尾删等接口:
3.测试程序
代码如下:
void test()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListPushFront(&s, 4);
SeqListPushFront(&s, 5);
SeqListPrint(&s);
SeqListInsert(&s, 2, 7);
SeqListPrint(&s);
SeqListErase(&s, 2);
SeqListPrint(&s);
SeqListDestory(&s);
}
int main()
{
test();
return 0;
}
总结
经过对顺序表的实现,我们知道了各个接口的原理,同时发现顺序表的头插、头删、随机插入,随机删除相对于尾插和尾删都十分麻烦,原因在于我们必须要挪动数据,而且需要十分小心的控制插入或删除的边界,否则会导致插入的数据把原始数据覆盖等一系列问题。但对于链表,我们不需要这么麻烦,我们只需要改变指针的指向即可,对于链表的实现,我们下期再说。
|