前言:线性表是数据结构与算法的重中之重,所有具有线性逻辑结构的数据结构,都能称为线性表。这篇文章我们先来讨论线性表中的顺序表,顺序表和线性表都是后续实现栈,树,串和图等等结构的重要基础。
目录
❀简单介绍线性表
❀顺序表
❀顺序表的存储
❀动态存储
❀静态存储
❀静态存储与动态存储的优缺点
❀顺序表操作
❀1.初始化顺序表
❀2.销毁顺序表
❀3.插入数据
❀插入数据之判断已满否
❀插入操作之尾插
❀插入操作之头插
❀插入数据之插入指定位置
❀4.删除数据
❀删除数据之尾删
❀删除数据之头删
❀删除数据之删除指定位置
❀5.查找顺序表单个数据
❀⭐6.打印顺序表中所有数据
❀结语
简单介绍线性表
对于某种数据结构的存储特点可以从两个方面来看:逻辑结构,物理结构。
逻辑结构:由人想像出来的抽象结构。
物理结构:在内存上的存储结构。
下面要介绍的顺序表——逻辑结构上,是一段依次排列的、连续存储的数据;在物理结构上,也是连续存储在内存上的。也就是说,顺序表在逻辑结构和物理结构上都是线性的(而只要在逻辑结构上是线性的,它就是线性表)。
顺序表
因为顺序表在物理结构上是连续存储的,所以顺序表的本质就是数组。但是数据结构与算法需要对数据进行相关的管理(如增删查改),因此,我们需要对存储数据的数组进行封装。
顺序表的存储
我们用结构体来封装顺序表,结构体内除了包括存储数据的数组之外,我们再定义两个成员,用来保存顺序表的最大存储容量(capacity)和顺序表的实际存储的有效数据(size)。
动态存储
//顺序表中数据类型
typedef int SLdatatype;
//顺序表存储结构
typedef struct SeqList
{
SLdatatype* arr;
int capacity;
int size;
}SL;
i.动态存储需要申请动态数组,通过调用动态内存管理函数来申请空间,然后通过指针arr保存申请空间的地址来形成动态数组。
ii.动态存储的顺序表,使用的是堆上的空间。
静态存储
#define M 100
//顺序表中数据类型
typedef int SLdatatype;
//顺序表存储结构
typedef struct SeqList{
SLdatatype arr[M];
int capacity;
int size;
}SL;
i.静态存储,只需直接创建数组即可。数组大小是定死的。
ii.静态存储的顺序表,使用的是栈上的空间。
静态存储与动态存储的优缺点
然而在实际编码项目中,出现过量的空间浪费或者空间不足都有可能导致严重的问题。空间浪费会消耗不必要的存储成本; 空间不足,会导致数据的泄露或者丢失。这些情况都会对管理者和用户造成严重损失。
因此,我们优先使用申请空间自由度高的动态存储来实现顺序表(以下操作均以动态存储为基础来实现)。
顺序表操作
下方非重点操作打了星星。
1.初始化顺序表
//初始化
void SLInit(SL* ps)
{
//全部置空置零
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
将顺序表的成员全部置空置零。
2.销毁顺序表
//销毁顺序表
void SLDestroy(SL* ps)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
free(ps->arr);//释放动态数组
SLInit(ps);//初始化顺序表
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.释放动态数组。
iii.初始化顺序表。
3.插入数据
插入新的数据就会占用顺序表容量,因此我们要确保顺序表有充足的容量,如果不够,我们得进行扩容。
插入数据之判断已满否
void SLCheckSpace(SL* ps)
{
if (!ps)
{
printf("empty ptr\n");
return;
}
if (ps->capacity == ps->size)//空间已满
{
int newCapacity = 1;
if (ps->capacity)
{
newCapacity = ps->capacity;
}
int* temp = (int*)realloc(ps->arr, sizeof(SLdatatype) * 2 * newCapacity);//扩容
if (temp)//扩容成功
{
ps->arr = temp;//接收扩容后地址
ps->capacity = 2 * newCapacity;//修改顺序表容量
}
}
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.如果空间已满,我们就进行扩容。扩容方式:将容量扩大到原来的两倍。
iii.修改顺序表扩容后的容量ps->capcity。
!!注意!!如果顺序表刚进行初始化操作,容量是零,此时容量乘2之后还是零。
因此,我们要设置默认值newcapacity为1。当容量为0的时候,使newcapacity不变;当容量不为零时,使newcapacity等于原容量.
PS:为什么是要扩大到原来的两倍呢?
因为,如果我们在容量不够的时候,只扩容多一个数据单位的容量,那么在下次插入时,又要进行一次扩容。虽然没有空间浪费,但是次数繁多的扩容操作,会让程序的时间效率大大的降低。所以,我们采用这种扩容方式,这样就能大大减少扩容的次数(因为每次都使得顺序表的容量呈指数级增长),同时这样的操作所产生的浪费也是可以控制的。
再补充一点,我们也可以让顺序表的容量每次扩大到原来的3倍,4倍,5倍都可以,按照实际情况来选择即可。
插入操作之尾插
//尾插
void SLPushBack(SL* ps, SLdatatype x)
{
if (!ps)//检查顺序表指针
{
printf("empty ptr!\n");
return;
}
SLCheckSpace(ps);//检查扩容
ps->arr[ps->size++] = x;//尾插后,size++
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.进行检查扩容。
iii.直接在顺序表最后的位置插入新数据(索引为ps->size)。
iiii.顺序表有效数据数ps->size++。
插入操作之头插
//头插
void SLPushFront(SL* ps, SLdatatype x)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
SLCheckSpace(ps);
int i = ps->size;
while (i >= 1)
{
ps->arr[i] = ps->arr[i - 1];
i--;
}
ps->arr[0] = x;
ps->size++;
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.检查扩容。
iii.将所有的数据往后挪一个数据单位。要从后面的数据开始依次往后挪,否则较后面的数据会被覆盖导致丢失。
iiii.将新数据插入顺序表第一个位置。
iiiii.顺序表有效数据ps->size++。
插入数据之插入指定位置
//插入指定位置数据
void SeqListInsert(SL* ps, SLdatatype x, int pos)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
if (pos < 0 || pos >= ps->size)
{
printf("wrong pos!\n");
return;
}
SLCheckSpace(ps);
for (int i = ps->size; i > pos; i--)//pos后数据往后挪1位
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;//插入数据
ps->size++;//size加一
return 0;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断插入位置pos是否合法。
iii.检查扩容。
iiii.将pos往后的数据都向后挪动一个数据单位。要从后面的数据开始依次往后挪,否则较后面的数据会被覆盖导致丢失。
iiiii.插入新数据到pos位置。
iiiiii.顺序表有效数据容量ps->size++。
4.删除数据
删除数据之尾删
//尾删
void SLPopBack(SL* ps, SLdatatype* x)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
if (ps->size == 0)
{
printf("无数据可删!\n");
return;
}
*x = ps->arr[--ps->size];//x接收删除的数据
//最终,size会被减1,删除的数据虽然仍保存在数组中,但不影响后续操作。
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断顺序表是否还有数据可删除。
iii.用x接收删除的数据。
iiii.顺序表有效数据ps->size--。
!!注意!!虽然顺序表中,实际上还存储着删除了的数据,但是这个数据由于ps->size的减小,已经无法访问或者使用,并且该残留数据也不会影响后续的操作。
删除数据之头删
//头删
void SLPopFront(SL* ps, SLdatatype* x)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
if (ps->size == 0)
{
printf("无数据可删!\n");
return;
}
*x = ps->arr[0];//接收删除的数据
int i = 1;
while (i < ps->size)//数据整体往前挪
{
ps->arr[i - 1] = ps->arr[i];
i++;
}
ps->size--;//size减1
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断顺序表是否还有数据可删除。
iii.用x接收删除的数据。
iiii.将第一位往后的所有数据都往前挪一位,从而覆盖第一位数据。要从前面的数据开始依次往前挪,否则较前面的数据会被覆盖导致丢失。
iiii.顺序表有效数据数ps->size--。
!!注意!!虽然顺序表经过该操作之后,ps->arr[size]上还保存着数据,但是这个数据由于ps->size的减小,已经无法访问或者使用,并且该残留数据也不会影响后续的操作。
删除数据之删除指定位置
//删除指定位置数据
void SeqListErase(SL* ps, int pos)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
if (pos < 0 || pos >= ps->size)
{
printf("wrong pos!\n");
return;
}
if (ps->size == 0)
{
printf("无数据可删!\n");
return;
}
for (int i = pos + 1; i < ps->size; i++)
{
ps->arr[i - 1] = ps->arr[i];
}
ps->size--;
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断插入位置pos是否合法。
iii.判断顺序表是否还有数据可删除。
iiii.将pos往后的数据都向前挪动一个数据单位。要从前面的数据开始依次往前挪,否则较前面的数据会被覆盖导致丢失。
iiiii.顺序表有效数据ps->size--。
5.查找顺序表单个数据
//查找顺序表单个数据
int SeqListFind(SL* ps, SLdatatype x)
{
if (!ps)
{
printf("empty ptr!\n");
return -1;
}
if (ps->size == 0)
{
printf("顺序表为空!\n");
return -1;
}
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
printf("数据x在顺序表的 %d 索引处\n", i);
return i;
}
}
printf("不存在这样的数据x\n");
return -1;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断顺序表是否有数据。没有数据就没必要查找了。
iii.循环遍历顺序表来查找。找到了就返回其索引;没找到就打印数据不存在的信息,返回-1。
⭐6.打印顺序表中所有数据
(实际打印方式不只下方代码一种,可根据实际情况设计打印操作)
//输出顺序表数据
void SLPrint(SL* ps)
{
if (!ps)
{
printf("empty ptr!\n");
return;
}
if (ps->size == 0)
{
printf("顺序表为空!\n");
return;
}
printf("当前顺序表数据为:");
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
return;
}
i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。
ii.判断顺序表是否有数据。没有数据就没必要打印了。
iii.循环遍历顺序表来打印。
结语
看完这篇博客,相信你已经对算法复杂度有了深刻认识了。有什么疑问和困惑欢迎来评论区留言!!🤩我一定尽力及时解答!!制作不易,求关注!!求点赞!!之后还会有更多有用的干货博客会发出哦!!欢迎做客我的主页!!❤❤Elnaij-CSDN博客❤❤