作者:热爱编程的小y
专栏:C语言数据结构
格言:能打败你的只能是明天的你
一、概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表是线性表的一种,他的孪生兄弟是链表。
二、结构
顺序表一般可以分为:
-
静态顺序表
使用定长数组存储元素。
-
动态顺序表
使用动态开辟的数组存储。
三、接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态地分配空间大小,所以下面我们实现动态顺序表。
(一)准备工作
1.创立文件
我们需要创立三个文件,分别是SeqList.c,SeqList.h,Test.c。
SeqList.h 内包含引用的头文件,函数的声明,结构体的声明,宏的声明与定义。
SeqList.c 内包含函数的主体,增删查改的具体过程在这里实现。
Test.c 负责对各项功能的测试与具体运用。
2.函数与结构体的定义
我们需要定义一个结构体类型用于数据的存放,结构体内需要包含三个参数,分别是一个指针,一个表示当前已存放的数据个数的整形,一个表示当前最多存放的数据个数的整形。因为我们不仅需要把数据存放进去,还要知道它的存放位置便于后续的更改,第三个参数则是确保开辟的空间大小在一个合理的范围内。具体如下。
typedef struct SeqList
{
SLDateType* a;//存放数据的地址
int size;//当前已存放的数据个数
int capacity;//当前最多存放的数据个数
}SeqList;
因为我们并不能确定存放数据的类型是什么,所以我们需要重新命名一个类型,就像这样。
typedef int SLDateType;//想修改顺序表内数据的类型可以直接在这修改
之后不论是想存放什么类型的数据,只要在这里修改一遍就可以了。
我们还需要定义各种函数来分别实现各种功能,功能就不一一介绍了,具体如下。
// 对数据的管理:增删查改
//初始化
void SeqListInit(SeqList* ps);
//销毁
void SeqListDestroy(SeqList* ps);
//显示(打印)
void SeqListPrint(SeqList* ps);
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
//头插
void SeqListPushFront(SeqList* ps, SLDateType x);
//头删
void SeqListPopFront(SeqList* ps);
//尾删
void SeqListPopBack(SeqList* ps);
// 顺序表查找
void SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
(二)功能实现
-
初始化与销毁
我们先给定一个内存大小初始值,按照这个初始值通过malloc或者calloc函数给参数ps->a开辟空间,
#define INIT_CAPACITY 4
我们将初始值定义成一个宏,方便随时修改。
空间有开辟失败的可能性(虽然可能性很小很小就是了),因此还要对其进行排除。
销毁顺序表的时候我们用free函数来释放掉里面的空间,并顺带把原先指向存放数据地址的指针赋为空,避免其成为野指针。
//初始化
void SeqListInit(SeqList* ps)
{
ps->a = (SLDateType*)malloc(INIT_CAPACITY * sizeof(SLDateType));
if (NULL == ps->a)
{
perror("SeqListInit::calloc");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
return;
}
//销毁
void SeqListDestroy(SeqList* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
return;
}
-
头插与尾插
先说明一下,头插就是在顺序表的开头插入数据,尾插则是在顺序的末尾插入数据。
我们进行头插操作时,因为不能直接硬插,而是要先腾出空间才能插入,而且不能打乱后面数据的顺序,所以我们需要把所有数据由末向前依次往后挪一个位置。结束之后要记得让size+1。
相比头插,尾插就方便多了,我们可以直接插入数据,因为size的值不仅是已有数据的个数也是我们插入的下一个数据的地址。
不管是头插还是尾插,我们都需要先进行一个判断是否扩容的操作,判断条件很简单,只需要判断size与capacity就是已有数据的个数与最大空间是否相等了即可。与开辟空间一样,扩容也存在失败的可能性,也需要进行排除。
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
/*增容*/
if (ps->size == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType)*ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->size++] = x;
return;
}
//头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
/*扩容*/
if (ps->size == ps->capacity || ps->size + 1 == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
int i = 0;
for (i = ps->size; i>0; i -- )
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
return;
}
-
头删与尾删
在进行头删操作的时候,我们就不需要像头插一样考虑空间的问题了,我们只需要对头部数据进行重新赋值,效果就等同于删除了。因此我们需要从第二个数据开始,赋值给它的前一个数据,依次向后推,直到最后一个数据也完成对前一个数据的赋值为之。
尾插则更简单了,只需要让控制顺序表数据个数的size-1即可。
注意:在进行上述删除操作之前,都需要对size的值进行一个判断,当它为0时理应终止删除,即让size停止自减操作,否则size有可能减成负数,而导致后续插入操作出现问题。
//尾删
void SeqListPopBack(SeqList* ps)
{
if (0 == ps->size)
return;
ps->size--;
}
//头删
void SeqListPopFront(SeqList* ps)
{
if (0 == ps->size)
return;
int i = 0;
for (i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
return;
}
-
在指定位置插入与删除
不同于尾插尾删,要想在指定位置进行数据的插入与删除,需要考虑后面数据的影响,因此它与头插头删类似,只不过起始地址不同罢了。
类似于头插,在指定位置插入时,也要把后续数据不变顺序地向后挪一个空间,再把新数据插入到空出来的那个空间当中
类似于头删,我们只需要从指定位置的后一个数据开始,向前赋值,依次往后直到最后一个数据。
//在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
/*扩容*/
if (ps->size == ps->capacity || ps->size + 1 == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
int i = 0;
for (i = ps->size; i > pos; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x;
ps->size++;
return;
}
//删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
if (0 == ps->size)
return;
if (pos > ps->size-1)
return;
int i = 0;
for (i = pos; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
return 0;
}
-
数据的显示与查找
显示与查找操作比较简单,都只用直接打印出来即可,就不过多介绍了,直接看代码。
//显示(打印)
void SeqListPrint(SeqList* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
return;
}
//查找
void SeqListFind(SeqList* ps, SLDateType x)
{
int i = 0, flag = 0;
for (i = 0; i < ps->size; i++)
{
if (x == ps->a[i])
{
printf("找到了,下标是:%d\n", i);
flag++;
}
}
if (flag == 0)
printf("没找到\n");
return;
}
四、代码呈现
最后附上完整代码:
SeqList.h部分
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#define INIT_CAPACITY 4
typedef int SLDateType;//想修改顺序表内数据的类型可以直接在这修改
typedef struct SeqList
{
SLDateType* a;//存放数据的地址
int size;//当前已存放的数据个数
int capacity;//当前最多存放的数据个数
}SeqList;
// 对数据的管理:增删查改
//初始化
void SeqListInit(SeqList* ps);
//销毁
void SeqListDestroy(SeqList* ps);
//显示(打印)
void SeqListPrint(SeqList* ps);
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
//头插
void SeqListPushFront(SeqList* ps, SLDateType x);
//头删
void SeqListPopFront(SeqList* ps);
//尾删
void SeqListPopBack(SeqList* ps);
// 顺序表查找
void SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
SeqList.c部分
#pragma once
#include"SeqList.h"
//初始化
void SeqListInit(SeqList* ps)
{
ps->a = (SLDateType*)malloc(INIT_CAPACITY * sizeof(SLDateType));
if (NULL == ps->a)
{
perror("SeqListInit::calloc");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
return;
}
//销毁
void SeqListDestroy(SeqList* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
return;
}
//显示(打印)
void SeqListPrint(SeqList* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
return;
}
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
/*增容*/
if (ps->size == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType)*ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->size++] = x;
return;
}
//尾删
void SeqListPopBack(SeqList* ps)
{
if (0 == ps->size)
return;
ps->size--;
}
//头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
/*扩容*/
if (ps->size == ps->capacity || ps->size + 1 == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
int i = 0;
for (i = ps->size; i>0; i -- )
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
return;
}
//头删
void SeqListPopFront(SeqList* ps)
{
if (0 == ps->size)
return;
int i = 0;
for (i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
return;
}
//查找
void SeqListFind(SeqList* ps, SLDateType x)
{
int i = 0, flag = 0;
for (i = 0; i < ps->size; i++)
{
if (x == ps->a[i])
{
printf("找到了,下标是:%d\n", i);
flag++;
}
}
if (flag == 0)
printf("没找到\n");
return;
}
//在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
/*扩容*/
if (ps->size == ps->capacity || ps->size + 1 == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * 2);
/*增容失败*/
if (tmp == NULL)
{
perror("SeqListPushFront::realloc");
return;
}
/*增容成功*/
ps->a = tmp;
ps->capacity *= 2;
}
int i = 0;
for (i = ps->size; i > pos; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x;
ps->size++;
return;
}
//删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
if (0 == ps->size)
return;
if (pos > ps->size-1)
return;
int i = 0;
for (i = pos; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
return 0;
}