1.什么是顺序表
顺序表是一种线性表的存储结构,它的元素按照顺序依次存放在一块连续的存储空间中。顺序表可以通过下标来访问和操作其中的元素,因此具有随机访问的特点。
顺序表的优点是存储结构简单,访问元素的时间复杂度为O(1),即常数时间。同时,顺序表的插入和删除操作也比较简单,时间复杂度为O(n),其中n为表中元素的个数。
尽管stl中有相应的函数,但作为数据结构的初学者,顺序表还是很有意义学习的。
2.顺序表包含哪些部分
作为一个集成结构,它具有,初始化,销毁,扩容,头插,尾插,头删,尾删,任意删除和插入功能。如头文件所示:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct seqlist {
SLDataType* a;//数组
int size;//记录
int capacity;//最大容量
}SL;
void SLInit(SL* psl);//初始化
void SLDestroy(SL* psl);//销毁
void SLPrint(SL* psl);//打印
void SLCheckCapacity(SL* psl);//检查最大访问
void SLPushBack(SL* psl, SLDataType x);//尾插
void SLPushFront(SL* psl, SLDataType x);//头插
void SLPopBack(SL* psl);//尾删
void SLPopFront(SL* psl);//前删
// 任意下标位置的插入删除
void SLInsert(SL* psl, int pos, SLDataType x);
void SLErase(SL* psl, int pos);
首先需要创建一个结构体SeqList,包含数组,当前数据大小,和最大容量三个参数。通过实时变化动态控制顺序表大小。
3.实现各部分函数功能。
1.初始化
void SLInit(SL* psl)
{
assert(psl);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
该函数有一个参数,即为所创建的结构体类型指针,记得一定是指针,否则无法进行函数局部内修改。分别将三个量赋为零。初始化完成。
2.创建一个扩容函数。
void SLCheckCapacity(SL* psl)
{
assert(psl);
if (psl->capacity == psl->size)
{
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
SLDataType* tem = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newcapacity);
if (tem == NULL)
{
return ;
}
psl->a = tem;
psl->capacity = newcapacity;
}
}
老规矩,assert断言一下。
这里我们思路是看当前空间是否等于最大容量,如果相等则表示内存上限,需要扩容。
接下来一个小细节,使用一个三目运算符,将初始开辟和再次开辟巧妙结合,之后设置一个临时变量开辟空间,防止开辟失败污染原始数据。
进行判断后我们将开辟空间和最新容量返回顺序表。
3.创建头插和尾插函数
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;
}
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
函数参数都为结构体指针和待插入数x.
老规矩assert断言
由于是插入,所以判断是否扩容,防止数组越界。
尾插比较简单,仅需将数据放在size的位置,值得注意的是,size我们每次后置++,所以size的位置就是,原数据下一个位置。
头插的思路则需要将整个数组向后移动一位,再将x放入a[0]。
记得size++,因为是插入。
4.任意位置插入。
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos>=0&&pos <= psl->size);
SLCheckCapacity(psl);
int end = psl->size - 1;
for (int i = end; i >= pos; i--)
{
psl->a[i + 1] = psl->a[i];
}
psl->a[pos] = x;
psl->size++;
}
三个参数,比以往多了一个Pos代表要插入位置。
老规矩,assert断言,并且判断所给位置是否合理。
任意插入类似于头插,将pos之后统一后运动即可。控制好下标就好。
5.删除系统,头删,尾删,随意删。
void SLPopBack(SL* psl)
{
assert(psl);
psl->size--;
}
void SLPopFront(SL* psl)
{
assert(psl);
int end = psl->size - 1;
for (int i = 1; i <= end; i++)
{
psl->a[i - 1] = psl->a[i];
}
psl->size--;
}
void SLErase(SL* psl, int pos)
{
assert(psl);
for (int i = pos; i < psl->size - 1; i++)
{
psl->a[i] = psl->a[i + 1];
}
psl->size--;
}
尾删依旧最简单,使用“以覆盖代替删除”,直接将size--即可,不访问即为删除。
值得注意的是这几个删除函数不用扩容函数,仅需assert一下。
要删除的位置直接用下一个位置数据覆盖就好,每个函数后需要size--.因为是删除函数。
6.销毁空间
void SLDestroy(SL* psl)
{
assert(psl);
if (psl->a != NULL)
{
free(psl->a);
psl->a = NULL;
psl->size = 0;
psl->capacity=0;
}
}
因为是动态内存管理,所以一定要释放内存,防止内存泄漏。先free,再将指针置空。各数据归零。
4.运行调试。
写一个test文件来运行各个功能。
#include<stdio.h>
#include"seqlist.h"
void TestSL1()
{
SL sl;
SLInit(&sl);
//尾插
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl);
//头插
SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPushFront(&sl, 7);
SLPushFront(&sl, 8);
SLPrint(&sl);
//尾删
SLPopBack(&sl);
SLPrint(&sl);
//头删
SLPopFront(&sl);
SLPrint(&sl);
//任意插入
SLInsert(&sl, 4, 18);
SLPrint(&sl);
//任意删除
SLErase(&sl, 3);
SLPrint(&sl);
SLPrint(&sl);
SLDestroy(&sl);
}
int main()
{
TestSL1();
return 0;
}
接下来就坐等f5:
YES!!!成功,各个功能正常,快去亲手尝试吧!!!
创作不易,感谢三联