目录
一、顺序表的定义
二、顺序表的分类
1.静态顺序表
2.动态顺序表
三、动态顺序表的实现
1.初始化
2.增添数据
<2.1>从顺序表尾部插入数据
<2.2>从顺序表头部插入数据
<2.3>从任意位置插入数据
<2.4>用复用函数的方式实现顺序表的头插和尾插
3.删除数据
<3.1>从顺序表尾部删除一个数据
<3.2> 从顺序表头部删除一个数据
<3.3>从顺序表任意位置删除一个数据
<3.4>用复用函数的方式实现顺序表的头删和尾删
4.遍历顺序表
5.查找某个数据在顺序表中的位置
6.销毁顺序表
7.运行效果
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
比如我们任意创建任何类型的数组:
int arr[20];
char arr1[8];
int *P=malloc(sizeof(int)*15);
struct S
{
int i;
float f;
}L[6];
在这里无论是如何类型的数组(arr、arr1、L),甚至是一块动态开辟的一整块连续可以存放元素空间的P,在这都是用一段物理地址连续的存储单元依次存储数据元素的线性结构,都可以叫做顺序表。
注:在线性表中数据要连续存储(即有效数据之间不能存在没有任何数据(或存在无效数据)的空间)。
静态顺序表顾名思义就是一块不可增添或删除空间的定长顺序表。
所以我们可以在栈上直接定义一个数组来实现:
静态顺序表由于空间大小不可改变,具有局限性一般不常用。
动态顺序表就是可以自己按需要增添或删除空间的不定长顺序表
一般用指针来管理动态顺序表的空间:
struct Seqlist
{
DataType* Data;//可以使用malloc和realloc对其空间进行动态分配
size_t size;//记录有效数据的个数
size_t capacity;//记录空间可以存储元素空间的总大小
}Seqlist;
动态顺序表相对于静态顺序表更灵活。
只要我们会实现动态顺序表,静态顺序表也不在话下了。所以本期博客主要对动态顺序表的实现进行讲解:
下面我们以此类型的结构体对动态顺序表进行实现:
typedef int DataType;
struct Seqlist
{
DataType* Data;//可以使用malloc和realloc对其空间进行动态分配
size_t size;//记录有效数据的个数
size_t capacity;//记录空间可以存储元素空间的总大小
}Seqlist;
注:这里的对int类型进行重定义是为了我们更好的看懂Data只是一种数据而不仅仅是int类型。所以我们在使用顺序表时存储的数据并不限制于int类型,这里仅仅是举例。
我们创建结构体后将要对其初始化(用结构体指针来改变结构体中成员的数据):
void InitSeqList(struct Seqlist* ps)
{
assert(ps);//传入的不能是空指针
ps->capacity = 0;
ps->Data = NULL;
ps->size = 0;
}
void SeqListPushBack(struct Seqlist* ps,DataType x)
{
assert(ps);//传入的不能是空指针
if (ps->capacity == ps->size)//判断是否需要扩容
{
size_t NewCapacity = ps->capacity + 5;//每次扩容增加5个元素的容量
//由于realloc可能扩容失败所以创建一个临时变量来接收它的返回值
DataType* temp = (DataType*)realloc(ps->Data, sizeof(DataType) * NewCapacity);
if (temp == NULL)//扩容失败
{
perror("realloc");
return;
}
//扩容成功
ps->Data = temp;
ps->capacity = NewCapacity;
}
ps->Data[ps->size] = x;
ps->size++;
printf("插入成功\n");
}
void SeqListPushFront(struct Seqlist* ps, DataType x)
{
assert(ps);//传入的不能是空指针
if (ps->capacity == ps->size)//判断是否需要扩容
{
size_t NewCapacity = ps->capacity + 5;//每次扩容增加5个元素的容量
//由于realloc可能扩容失败所以创建一个临时变量来接收它的返回值
DataType* temp = (DataType*)realloc(ps->Data, sizeof(DataType) * NewCapacity);
if (temp == NULL)//扩容失败
{
perror("realloc");
return;
}
//扩容成功
ps->Data = temp;
ps->capacity = NewCapacity;
}
DataType temp1, temp2 = x;
//将输入的数据插入到表头的位置,将其余的数据向后挪动一位
for (int j = 0; j <= ps->size; j++)
{
temp1 = ps->Data[j];
ps->Data[j] = temp2;
temp2 = temp1;
}
ps->size++;
printf("插入成功\n");
}
void SeqListInsert(struct Seqlist* ps, size_t pos, DataType x)//参数pos传入要插入数组的位置
{
assert(ps);//传入的不能是空指针
if (pos > ps->size|| pos < 1)//由于是顺序表,有效数据之间不能有未利用的空间存在,所以要判断插入位置是否合理
{
printf("插入位置不合理\n");
return 0;
}
if (ps->capacity == ps->size)//判断是否需要扩容
{
size_t NewCapacity = ps->capacity + 5;//每次扩容增加5个元素的容量
//由于realloc可能扩容失败所以创建一个临时变量来接收它的返回值
DataType* temp = (DataType*)realloc(ps->Data, sizeof(DataType) * NewCapacity);
if (temp == NULL)//扩容失败
{
perror("realloc");
return;
}
//扩容成功
ps->Data = temp;
ps->capacity = NewCapacity;
}
DataType temp1, temp2 = x;
//将输入的数据插入到pos的位置,将pos之后的数据向后挪动一位
for (int j = pos - 1; j <= ps->size; j++)//由于数组元素是从0开始的,所以pos在数组中的位置应为pos-1
{
temp1 = ps->Data[j];
ps->Data[j] = temp2;
temp2 = temp1;
}
ps->size++;
printf("插入成功\n");
}
既然我们有可以在任意位置向顺序表插入数据的函数,那我们可以直接利用此函数来实现顺序表的头插和尾插 :
(1)头插
void SeqListPushFront(struct Seqlist* ps, DataType x)
{
SeqListInsert(ps, 1, x);
}
(2)尾插
void SeqListPushBack(struct Seqlist* ps,DataType x)
{
SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(struct Seqlist* ps)
{
assert(ps);//传入的不能是空指针
if (ps->size == 0)//判断顺序表中是否存有数据
{
printf("表中无数据可以删除了\n");
return;
}
ps->Data[ps->size - 1] = 0;//这里置0没有什么实际意义,只是提示一下这里的数据没有实际意义了
ps->size--;//这一步才是关键,因为整个顺序表是根据size来判断数据是否有意义
printf("删除成功\n");
}
void SeqListPopFront(struct Seqlist* ps)
{
assert(ps);//传入的不能是空指针
if (ps->size == 0)//判断顺序表中是否存有数据
{
printf("表中无数据可以删除了\n");
return;
}
//将头数据后每个数据向前移动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->Data[i] = ps->Data[i + 1];
}
ps->size--;
printf("删除成功\n");
}
void SeqListErase(struct Seqlist* ps,size_t pos)//参数pos传入要删除数组的位置
{
assert(ps);//传入的不能是空指针
if (pos > ps->size || pos < 1)//判断要删除数据的位置是否合理
{
printf("此位置无数据可删除\n");
return;
}
//将要删除数据的位置后每个数据向前移动一位
for (int i = pos; i < ps->size - 1; i++)
{
ps->Data[i] = ps->Data[i + 1];
}
ps->size--;
printf("删除成功\n");
}
既然我们有可以在任意位置向顺序表删除数据的函数,那我们可以直接利用此函数来实现顺序表的头删和尾删 :
(1)头删
void SeqListPopFront(struct Seqlist* ps)
{
SeqListErase(ps,1);
}
(2)尾删
void SeqListPopBack(struct Seqlist* ps)
{
SeqListErase(ps,ps->size);
}
void SeqListPrint(struct Seqlist* ps)
{
assert(ps);//传入的不能是空指针
if (ps->size == 0)//判断顺序表中是否有数据
{
printf("顺序表中无数据\n");
return;
}
//打印顺序表中的每个数据
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->Data[i]);
}
printf("\n");
}
int SeqListFind(struct Seqlist* ps, DataType x)
{
assert(ps);//传入的不能是空指针
for (int i = 0; i < ps->size; i++)
{
if (ps->Data[i] == x)
{
printf("该数据在表中第%d个位置\n", i + 1);
return i;
}
}
printf("未在表中找到该数据\n");
return -1;
}
void SeqListDestroy(struct Seqlist* ps)
{
assert(ps);//传入的不能是空指针
if (ps->Data)
{
free(ps->Data);
ps->Data = NULL;
ps->capacity = 0;
ps->size = 0;
}
printf("销毁成功\n");
}
int main()
{
struct Seqlist S;
InitSeqList(&S);
SeqListPrint(&S);
SeqListPushBack(&S, 1);
SeqListPrint(&S);
SeqListPushBack(&S, 2);
SeqListPrint(&S);
SeqListPushBack(&S, 3);
SeqListPrint(&S);
SeqListPushFront(&S, 6);
SeqListPrint(&S);
SeqListInsert(&S, 2, 0);
SeqListPrint(&S);
SeqListPopBack(&S);
SeqListPrint(&S);
SeqListPopFront(&S);
SeqListPrint(&S);
SeqListErase(&S, 1);
SeqListPrint(&S);
SeqListFind(&S, 0);
SeqListDestroy(&S);
SeqListPrint(&S);
return 0;
}
本期博客就到这啦,该文章是属于数据结构专栏代码量较多,主要是理解和实现。如果有错误还请各位看客不吝赐教,谢谢大家啦~
我们下一期见~