动态顺序表的实现
实现动态顺序表的代码多种多样,不过大体思路一致,我们提供一个思路,重在理解,才会做题,才能看懂其他的形式。
- 我们将代码分为三个文件实现,分别为:
SeqList.h: 包含所有需要的头文件,定义以及接口函数的声明。
SeqList: 保存所有接口函数的定义和实现
test.c: 主函数,一般为打印菜单,调用接口函数,需求多样,我们仅实现头文件和接口函数。
头文件
由于我们要实现的是动态顺序表,所以存储数据的位置是我们开辟的动态内存空间。我们先编辑SeqList.h
//SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SQDateType;//未来如果想在顺序表中存储其他类型的数据,仅需要修改int为想存储的类型
typedef struct SeqList
{
SQDateType* a;//指向存储数据的内存空间
int size;//顺序表中的有效数据的个数
int capacity;//顺序表的容量,也就是最大存储数据数
}SL;
接口函数实现
完成以上准备,我们可以开始准备我们的接口函数了,实现对顺序表的增删查改等操作。
-
首先,对我们的顺序表进行初始化:
void SeqListInit(SL* ps) { ps->a = NULL; ps->size = 0; ps->capacity = 0; }
-
实现一个打印顺序表的函数
void SeqListPrint(SL* ps) { for(int i = 0; i < ps->size; i++) { printf("%d ",ps->a[i]);//注意,我们在修改存储数据的类型时,同时需要修改这里的占位符 } printf("\n"); }
-
我们继续分析,每次在顺序表中插入数据,我们都需要判断容量是否占满,如果占满,我们需要对我们的内存空间进行扩容。
考虑到频繁实现这样的操作,我们专门将这一操作分装成一个独立的函数,每次插入数据时,调用这个函数即可,省去了重复书写的麻烦。
void SeqListCheckCapacity(SL* ps) { if(ps->size == ps->capacity)//只有空间满,才需要扩容 { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; //确定新容量,一般扩容为原来的两倍,别忘了考虑容量为0时的情况(证明没有开辟过空间),这种情况下肯定是没有办法存储数据的,同时扩容后的效果为:0 * 2 = 0,相当于没有扩容。所以我们先判断容量是否为0,为0,那么我们给容量赋一个合理的数值,作为新容量(这里我们赋值4);否则,我们将容量扩大一倍,这种逻辑刚好可以用三目操作符实现。 SQDateType* tmp = (SQDateType*)realloc(ps->a, newcapacity * sizeof(SQDateType)); //我们创建一个新的指针变量来接收realloc的返回值,避免了开辟失败后返回NULL覆盖原有地址的情况。 if(tmp == NULL)//判断如果为NULL,则打印错误信息并退出函数。 { printf("realloc fail!\n"); exit(-1); } else//不为空,证明开辟(扩容)成功。 { ps->capacity = newcapacity; ps->a = tmp; } } }
-
好了,我们可以尝试实现一个尾插的接口函数了。
void SeqListPushBack(SL* ps, SQDateType x) { SeqListCheckCapacity(ps);//尾插需要插入新数据,调用SeqListCheckCapacity函数 ps->a[ps->size] = x; ps->size++;//插入后及时更新有效数据的个数 }
-
头插函数
void SeqListPushFront(SL* ps, SQDateType x) { //思路:将所有元素向后移动一位,再将新元素插入到表首 SeqListCheckCapacity(ps); for(int i = ps->size; i >= 1; i--) { ps->a[i] = ps->[i-1]; } ps->a[0] = x; ps->size++; }
-
尾删函数
void SeqListPopBack(SL* ps) { ps->a[ps->size-1] = 0;//这句语句可以省略,因为我们打印顺序表时是以size为基准的,size--再打印不会显示最后一个元素,而如果再在那个位置插入时就直接覆盖掉了 ps->size--; }
-
头删函数
void SeqListPopFront(SL* ps) { //思路:从第二个数据开始依次向左移动一位,将头数据覆盖 for(int i = 0; i < ps->size; i++) { ps->a[i] = ps->a[i+1]; } ps->size--; }
-
插入函数
void SeqListInsert(SL* ps, int pos, SQDateType x) { //这里的pos以及后面出现的pos不是下标,而是位置,例如下标为0的元素的位置为1 //思路:将pos位置(即下标为pos-1的元素的位置)及之后的所有元素向后移动一位,再将新元素放在指定的位置。 assert(pos < ps->size);//如果插入位置参数输入非法,则报错 SeqListCheckCapacity(ps); for(int i = ps->size; i > pos - 1; i--) { ps->a[i] = ps->a[i-1]; } ps->a[pos-1] = x; ps->size++; }
-
删除函数
void SeqListErase(SL* ps, int pos) { //思路:将pos位置后面的所有元素向前移动一位,覆盖掉pos位置的元素 assert(pos < ps->size); for(int i = pos - 1; i < ps->size; i++) { ps->a[i] = ps->a[i+1]; } ps->size--; }
-
查找函数
int SeqListFind(SL* ps, SQDateType x) { //思路:遍历即可 for(int i = 0; i < ps->size; i++) { if(ps->a[i] == x) { return i; } } return -1; }
-
修改函数
void SeqListModity(SL* ps, int pos, SQDateType x) { //思路:找到指定位置后直接修改即可 assert(pos < ps->size); ps->a[pos-1] = x; }
-
释放函数
void SeqListDestory(SL* ps) { free(ps->a); ps->a = NULL; ps->size = 0; ps->capacity = 0; }
至此我们完成了我们的接口函数定义实现部分,接口函数当然不止这些,不过这里也涵盖了最常见的接口函数,理解了这些,其他接口函数的实现也就如鱼得水。
完整代码
//SeqList.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
typedef int SQDateType;
typedef struct SeqList
{
SQDateType* a;
int size;
int capacity;
}SL;
void SeqListInit(SL* ps);
void SeqListPrint(SL* ps);
void SeqListCheckCapacity(SL* ps);
void SeqListPushBack(SL* ps, SQDateType x);
void SeqListPushFront(SL* ps, SQDateType x);
void SeqListPopBack(SL* ps);
void SeqListPopFront(SL* ps);
void SeqListInsert(SL* ps, int pos, SQDateType x);
void SeqListErase(SL* ps, int pos);
void SeqListModity(SL* ps, int pos, SQDateType x);
int SeqListFind(SL* ps, SQDateType x);
void SeqListDestory(SL* ps);
//SeqList.c
#include "SeqList.h"
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SQDateType* tmp = (SQDateType*)realloc(ps->a, newcapacity * sizeof(SQDateType));
if (tmp == NULL)
{
printf("%s\n", strerror(errno));
exit(-1);
}
else
{
ps->capacity = newcapacity;
ps->a = tmp;
}
}
}
void SeqListPushBack(SL* ps, SQDateType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
void SeqListPushFront(SL* ps, SQDateType x)
{
SeqListCheckCapacity(ps);
for (int i = ps->size; i >= 1; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
void SeqListPopBack(SL* ps)
{
ps->a[ps->size - 1] = 0;
ps->size--;
}
void SeqListPopFront(SL* ps)
{
for (int i = 0; i < ps->size ; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
void SeqListInsert(SL* ps, int pos, SQDateType x)
{
assert(pos < ps->size);
SeqListCheckCapacity(ps);
for (int i = ps->size; i > pos - 1; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos - 1] = x;
ps->size++;
}
void SeqListErase(SL* ps, int pos)
{
assert(pos < ps->size);
for (int i = pos - 1; i < ps->size; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
int SeqListFind(SL* ps, SQDateType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListModity(SL* ps, int pos, SQDateType x)
{
assert(pos < ps->size);
ps->a[pos - 1] = x;
}
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}