顺序表的概念及结构
顺序表是用一段物理地址连续的存储单元一次存储数据元素的线性结构,一般情况下采用数组存储,在数据上完成数据的增删查改
顺序表一般分为:
- 静态顺序表:使用定长数组存储元素
- 动态顺序表:使用动态开辟的数组存储
顺序表的实现
静态顺序表只适用于确定知道需要存储多少数据的场景,在现实中往往会出现空间开多了浪费,开少了又不够用的情况,实用性不强。所以往往使用动态顺序表,根据需要动态地开辟空间。
文件的创建
为了分工明确,我创建了三个文件:
- SeqList.h用于头文件的包含,函数的声明及顺序表结构的定义
- SeqList.c用于实现顺序表有关的函数(例如增、删、改、查等)
- test.c用于测试顺序表是否能够正常运行及使用
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data;//指向动态开辟的数组
int sz;//有效数据的个数
int capacity;//容量空间的大小
}SeqList;
void InitSeqList(SeqList* ps);
void CheckCapacity(SeqList* ps);//检查内存
void SeqListPopBack(SeqList* ps);//尾删
void SeqListPushBack(SeqList* ps, SLDataType x);//尾插
void SeqListPushFront(SeqList* ps, SLDataType x);//头插
void SeqListPopFront(SeqList* ps);//头删
void SeqListPrint(SeqList* ps);//打印
int SeqListFind(SeqList* ps, SLDataType x);//查找
void SeqListInsert(SeqList* ps, int pos, SLDataType x);//在指定位置插入
void SeqListErase(SeqList* ps, int pos);//在指定位置删除
void SeqLisrDestory(SeqList* ps);//销毁
这里将int类型重定义为SLDataType是为了后续若要让顺序表存储其他类型的数据,则可以直接在这个地方修改,提高了使用效率
SeqList.c
初始化
void InitSeqList(SeqList* ps)
{
ps->sz = 0;
ps->capacity = 4;
ps->data = (SLDataType*)malloc(sizeof(SLDataType) * 4);
if (ps->data == NULL)
{
perror("malloc");
exit(-1);
}
}
初始化时先将sz置为0(此时没有数据),同时提前先申请好4个字节以供使用,当ps->data 为 NULL时,说明申请空间失败,则打印失败的原因,同时终止程序
检查内存
void CheckCapacity(SeqList* ps)//检查内存
{
assert(ps);
if (ps->sz == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(int) * (ps->capacity) * 2);
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
ps->data = tmp;
}
}
当ps->sz和ps->capacity相等时,说明有效数据的个数已经到达了容量的最大值,即需要扩容。
扩容时使用tmp来接受realloc的返回值(防止出现申请内存失败而导致的内存泄漏问题,详情可看http://t.csdn.cn/thS2f)),
最后确保tmp没有问题后,再将tmp赋值给ps->data
打印
void SeqListPrint(SeqList* ps)
{
assert(ps);//防止传入空指针
for (int i = 0; i < ps->sz; i++)
{
printf("%d ", ps->data[i]);
}
printf("\n");
}
按照下标顺序依次访问顺序表中每个元素的,并将其打印出来。
尾删
void SeqListPopBack(SeqList* ps)//尾删
{
assert(ps);
assert(ps->sz);//当没有数据的时候删不了了
ps->sz--;
}
因为ps->sz代表的是有效数据的个数,当ps->sz–后,最后一个数字就已经不在被视为是有效数字,这样就达到了尾删的目的
尾插
void SeqListPushBack(SeqList* ps, SLDataType x)//尾插
{
assert(ps);
CheckCapacity(ps);//在插入之前先检查内存是否充足
ps->data[ps->sz] = x;
ps->sz++;//插入之后有效数据个数加1
}
头插
void SeqListPushFront(SeqList* ps, SLDataType x)//头插
{
assert(ps);
CheckCapacity(ps);//在插入之前先检查内存是否充足
int end = ps->sz;
while (end)
{
ps->data[end] = ps->data[end - 1];
end--;
}
ps->data[end] = x;
ps->sz++;
}
在头插之前先要将数据整体往后挪,对应的是代码中的while循环部分
在挪的时候要注意是从最后一个数据开始往后挪的,如果是从第一个数据往后挪的话,就会出现有的数据还没有挪走就已经被覆盖了
出了循环后,end对应的值为0,此时再将0下标的元素赋值为x,完成头插的操作
头删
void SeqListPopFront(SeqList* ps)//头删
{
assert(ps);
assert(ps->sz);
int begin = 1;
while (begin<ps->sz)
{
ps->data[begin - 1] = ps->data[begin];
begin++;
}
ps->sz--;
}
头删就是用第二个元素覆盖第一个元素,然后依次用后面的元素覆盖前面的元素,最后设置有效数据-1,就实现了头删的效果
查找
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->sz; i++)
{
if (ps->data[i] == x)
{
return i;
}
}
return -1;
}
通过下标依次访问每个元素,最后将所要查找的元素下标返回回去,如果没有找到,返回-1
在指定位置插入
void SeqListInsert(SeqList* ps,int pos ,SLDataType x)
{
assert(ps);
if (pos<0 || pos>ps->sz)//输入的插入位置不合法
{
assert(0);
}
CheckCapacity(ps);
int move = ps->sz;
while (move!=pos)
{
ps->data[move] = ps->data[move - 1];
move--;
}
ps->data[pos] = x;
ps->sz++;
}
当需要在指定位置插入时,先需要检查传入过来的下标是否合法,再检查内存是否足够,然后从最后一个元素(move下标)开始往后挪一位,直到move和pos相等,此时已有空余位置以供插入,再将x插入到顺序表中。
在指定位置删除
void SeqListErase(SeqList* ps, int pos)//在指定位置删除
{
assert(ps && ps->sz);
assert(pos >= 0 && pos < ps->sz);
int move = pos + 1;
while (move!=ps->sz)
{
ps->data[move-1] = ps->data[move];
move++;
}
ps->sz--;
}
在指定位置删除就是将pos下标指向的元素用该元素后面一个元素覆盖,依次向后往复,直到最后一个元素,最后再将有效数据-1,实现删除的效果
test.c
在该文件中主要是用于测试实现的顺序表的各项功能是否完好,在这里就不再展示代码了,读者可以在实现顺序表后自行测试。