① 在函数中判断结构体指针是否为空时采用assert函数,而不是用if语句判断。
② 函数的命名规则遵循:操作+结构类型名 的规则。例如 InitSqList 与DestroySqList。
③ 数据结构书中很多运用了C++的语法,而我们是用C语言来实现,因此编写规则与书上会有很多不同,但是思路是一样的。例如用malloc代替new,free代替delete,引用与指针的区别等。
④ 本文没有采用bool变量以及自定义的Status作为返回值。
线性表是一个相当灵活的数据结构,其长度可以根据需要而增加或缩短。线性表的顺序表示式指用一段地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序影像。这种存储结构的线性表称为顺序表(Sequential List)。特点是,逻辑上相邻的数据元素,物理次序上也是相邻的。
上面这段话摘自严蔚敏老师的数据结构一书。线性表中顺序表是最基本的一种数据结构,本章将对顺序表的原理进行介绍和具体实现。
我们定义的是动态的顺序表,相较于静态顺序表的优点是:空间能够灵活处理。我们需要多少空间就用多少空间,而不是像开辟定长数组一样把长度定死。
typedef int SLDataType;
typedef struct SL
{
//不考虑频繁扩容
SLDataType* SLa;
int size; //数据个数
int capacity; //空间容量
}SqList;
头文件中定义如是。其中定义SLDataType便于后续修改元素类型。SLa表示供动态使用的数组(指针),定义一个size和capacity分别表示当前数据个数和顺序表的容量。
功能实现
有了这个结构体,我们便可以对顺序表进行基本的操作:增删查改。
顺序表函数传参我们选择传结构体的地址,如果传参没有传结构体的地址,在函数中系统将会开辟一块结构体的临时拷贝,因此会占用多余的内存空间,而传结构体的地址则能避免这个问题。
void InitSqList(SqList* ps); //初始化
void DestroySqList(SqList* ps); //摧毁
void FrontInsertSqList(SqList* ps, SLDataType i);//头插
void FrontDeleteSqList(SqList* ps); //头删
void BackInsertSqList(SqList* ps, SLDataType i);//尾插
void BackDeleteSqList(SqList* ps); //尾删
void PrintSqList(SqList* ps); //打印
void CheckCapacitySqList(SqList* ps); //检查容量
//此处pos是位置,不是下标
void InsertAtSqList(SqList* ps, int pos, SLDataType i);//插入
void DeleteAtSqList(SqList* ps, int pos); //删除
//返回的是下标
int SearchSqList(SqList* ps, SLDataType i); //查找元素
void ModifySqList(SqList* ps, int pos,SLDataType i);//修改元素
咱们一个个来实现。
void InitSqList(SqList* ps)//初始化
{
assert(ps);
ps->SLa = NULL;
ps->size = 0;
ps->capacity = 0;
}
void FrontInsertSqList(SqList* ps,SLDataType i)
{
assert(ps);
//先判断空间是否足够,接着从后往前挪数据
CheckCapacitySqList(ps);
for (int end = ps->size - 1; end >= 0; end--)
{
ps->SLa[end + 1] = ps->SLa[end];
}
ps->SLa[0] = i;
ps->size++;
}
这里检查容量封装成一个函数,因为在各种插入中均需要调用。
void CheckCapacitySqList(SqList* ps)
{
//检查是否已满,满了就扩容
assert(ps);
if (ps->size == ps->capacity)
{
//如果满了就扩容
size_t NewCapacity = ps->capacity == 0 ? SqListMaxNum : ps->capacity * 2;
//先判断原容量是不是为0
SLDataType* tmp = (SLDataType*)realloc(ps->SLa, sizeof(SLDataType) * NewCapacity);
//当ps->a是空,realloc相当于malloc
if (tmp == NULL)
{
printf("扩容失败!\n");
exit(-1);
}
else
{
ps->SLa = tmp;
ps->capacity *= 2;
}
}
}
这里使用realloc而不是malloc是因为当第一个参数是空指针时,realloc可以当malloc用,且realloc比malloc使用更加方便,realloc会自己判断空间应该在哪开辟不会与其他内存空间冲突,而malloc只是单纯找一块空间之后开辟。
void FrontDeleteSqList(SqList *ps)
{
assert(ps);
if (ps->size > 0)
{
for (int begin = 0;begin < ps->size - 1; begin++)
{
ps->SLa[begin] = ps->SLa[begin + 1];
}
ps->size--;
}
else
{
printf("表空,表头删除失败!\n");
return;
}
}
将第一个元素开始后面的元素往前覆盖即可,最后size-1。
void BackInsertSqList(SqList* ps, SLDataType i)
{
assert(ps);
CheckCapacitySqList(ps);
ps->SLa[ps->size] = i;
ps->size++;
}
void BackDeleteSqList(SqList* ps)
{
assert(ps);
if (ps->size > 0)
ps->size--;
else
{
printf("表空,表尾删除失败!\n");
return;
}
}
这里只把size-1,尾部的元素不去修改因为不知道要如何赋值,因为不知道要赋值什么类型,也不知道要赋的具体值。
void InsertAtSqList(SqList* ps, int pos, SLDataType i)
{
//此处pos是位置,不是下标
assert(ps);
CheckCapacitySqList(ps);
if (pos > ps->size + 1 || pos <= 0)
{
printf("插入位置越界,表当前大小为:%d!\n",ps->size);
return;
}
else
{
for (int end = ps->size - 1;end >= pos - 1;end--)
{
ps->SLa[end + 1] = ps->SLa[end];
}
ps->SLa[pos - 1] = i;
ps->size++;
}
}
此处我写的是在元素的第pos个位置插入,pos指位置而不是下标。
可以理解成位置=下标+1;
void DeleteAtSqList(SqList* ps, int pos)
{
//pos是位置,不是下标
assert(ps);
if (ps->size > 0)
{
if (pos > ps->size || pos <= 0)
{
printf("删除位置越界,表当前大小为:%d!\n", ps->size);
return;
}
for (int begin = pos; begin <= ps->size - 1; begin++)
{
ps->SLa[begin - 1] = ps->SLa[begin];
}
ps->size--;
}
else
{
printf("表空,删除失败!\n");
return;
}
}
int SearchSqList(SqList* ps,SLDataType i)
{
assert(ps);
for (int begin = 0; begin < ps->size; begin++)
{
if (ps->SLa[begin] == i)
return begin;
}
return -1;
}
如果找到了该元素,返回的是该元素的下标,而不是位置。
void ModifySqList(SqList* ps, int pos,SLDataType i)
{
//pos是位置不是下标
assert(ps && pos <= ps->size&& pos > 0);
CheckCapacitySqList(ps);
ps->SLa[pos - 1] = i;
}
同样的pos是位置不是下标
线性表的效果跟数组有相似之处,可以说是改造于数组。实现线性表也是在学习完C语言之后对C语言掌握程度和理解的一次检验。
顺序表是学习数据结构的开端,实现较为简单,之后的数据结构会越来越复杂。
继续加油吧ヾ(◍°∇°◍)ノ゙