顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表一般分为静态顺序表和动态顺序表。
静态顺序表是使用定长数组存储元素。
简单静态顺序表结构体的初始化:
#define N 7
typedef int SLDataType;
struct Seqlist
{
SLDataType a[N];
int size;//记录数据个数
};
代码中数组a是用来存储要存入到顺序表中,很明显,该数组开辟的空间大小是固定的,所以有着无法优化的缺点——最大容量是固定的:
最大容量开小了,可能会导致元素存储不下,最大容量开大了会导致空间浪费严重。
故一般情况下顺序表一般都是使用动态顺序表。
动态顺序表是利用动态开辟来存储元素。
typedef int SLDataType;
typedef struct Seqlist
{
SLDataType* a;//指向动态开辟的数组
int size; //存储数据的个数
int capacity; //存储空间的大小
}SL;
动态顺序表的优点在于需要多少空间来存储数据我就能开辟多少空间,不会出现大规模空间浪费和空间不足的情况。
动态顺序表接口的实现主要要完成以下几个功能:增删查找改
// 顺序表初始化
void SeqlistInit(SL* psl);
检查空间,如果满了,进行增容
void SeqlistCheckCapacity(SL* psl);
//顺序表尾插
void SeqlistPushBack(SL* psl, SLDataType x);
//顺序表尾删
void SeqlistPopBack(SL* psl);
//顺序表头插
void SeqlistPushFront(SL* psl, SLDataType x);
//顺序表头删
void SeqlistPopFront(SL* psl);
//顺序表在pos位置插入x
void SeqlistInsert(SL* psl, size_t pos, SLDataType x);
//顺序表删除pos位置的值
void SeqlistErase(SL* psl, size_t pos);
//查找顺序表中的x的位置
int SeqlistFind(SL* psl, SLDataType x);
//修改顺序表中pos位置的值
void SeqlistModify(SL* psl, size_t pos,SLDataType x);
//销毁顺序表开辟的空间
void SeqlistDestroy(SL* psl);
//顺序表的打印
void SeqlistPrint(SL* psl);
这些功能中我主要介绍以下检查顺序表的容量、尾插、尾删、头插、头删和在指定位置插入或删除数据
void SeqlistCheckCapacity(SL* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
//利用realloc函数对顺序表进行动态增容
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
psl->a = tmp;
psl->capacity = newcapacity;
}
}
}
尾插大致思路:在检查完顺序表的容量之后将新增的数据插入到顺序表最末尾,同时顺序表里的元素增加1。
尾删则更加简单,只需将顺序表的元素个数减1即可,因为访问顺序表时,是根据顺序表的元素个数来访问的。
//尾插
void SeqlistPushBack(SL* psl, SLDataType x)
{
assert(psl);
SeqlistCheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;
}
//尾删
void SeqlistPopBack(SL* psl)
{
assert(psl);
if(psl->size > 0)
psl->size--;
}
//头插
void SeqlistPushFront(SL* psl, SLDataType x)
{
assert(psl);
SeqlistCheckCapacity(psl);
int end = psl->size - 1;
for (end; end >= 0; end--)
{
psl->a[end + 1] = psl->a[end];
}
psl->a[0] = x;
psl->size++;
}
头删则与头插相反,需要从前往后遍历,依次将所有元素往前移动一位。
//头删
void SeqlistPopFront(SL* psl)
{
assert(psl);
int begin = 0;
if (psl->size > 0)
{
//向前挪动数据
for (begin; begin < psl->size - 1; begin++)
{
psl->a[begin] = psl->a[begin + 1];
}
psl->size--;
}
}
在pos位置插入数据,首先得把pos位置后面(包括pos位置)的元素往后移一个位置,然后将要插入的数据添加到pos位置即可。
//在指定位置插入数据
void SeqlistInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
if (pos > psl->size)
{
printf("pos 越界:%d\n",pos);
return;
}
SeqlistCheckCapacity(psl);
if (psl->size == 0)
{
psl->a[0] = x;
psl->size++;
return;
}
//将end指向最后一个元素的下一个位置,避免循环时end为负数导致整型提升
//从而造成死循环2
int end = psl->size;
for (end;end>pos; end --)
{
psl->a[end] = psl->a[end -1];
}
psl->a[pos] = x;
psl->size++;
}
在指定位置删除数据则与上述相反,要将pos位置后面的元素往前移动一位,然后size减1就行。
void SeqlistErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
begin++;
}
psl->size--;
}
int SeqlistFind(SL* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
return i;
}
return -1;
}
顺序表的初始化:
//顺序表的初始化
void SeqlistInit(SL* psl)
{
assert(psl);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
查找和修改顺序表:二者一般结合在一起使用,需要先找到指定元素的位置才能对其进行修改,查找顺序表的元素在删除指定位置的元素和在指定位置插入元素中也有作用。
int SeqlistFind(SL* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
return i;
}
return -1;
}
void SeqlistModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos < psl->size);
psl->a[pos] = x;
}
顺序表的打印:
//顺序表的打印
void SeqlistPrint(SL* psl)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
顺序表的销毁:动态开辟的空间在程序结束之前应当将空间释放掉。
//顺序表的销毁
void SeqlistDestroy(SL* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
顺序表的内容就到这了,欢迎各位大佬莅临指教。