
--线性表是n个有相同特性的数据元素的有限序列,是广泛应用的数据结构。常见的:顺序表、链表、栈、队列、字符串……
--线性表,逻辑上是线性结构(连续的一条线)。物理结构不一定是连续的,线性表在物理上存储,通常以顺序结构、链式结构存储。

--定义:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况采用数组存储。(顺序表底层是数组)

那顺序表就是数组? --不!顺序表是对数组的封装,实现了常用的增删查改等接口。
--形象对比数组与顺序表:

--静态顺序表使用定长数组存储元素。
--不足:可能空间不够用,初始化给多有可能浪费。

--将 int 重新命名为 SLDataType; --size 指向有效数据的下一位;
--动态顺序表的空间按需申请

--两种顺序表在不同的应用场景中各有作用。
--实现动态顺序表,用以下三个文件:

//SeqList.h头文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
//定义动态顺序表的结构
typedef int SLTDataType;
typedef struct SeqList
{
SLTDataType* arr;//存储数据
int size;//有效数据个数
int capacity;//空间大小
}SL;//SL是对结构体重命名
//定义顺序表初始化函数声明
void SLInit(SL* ps);//SeqList.c文件(配合SeqList.h文件)
//定义、实现函数
#define _CRT_SECURE_NO_WARNINGS
#include"seqlist.h"//包含头文件
void SLInit(SL* ps)//指针接收地址
{
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}#define _CRT_SECURE_NO_WARNINGS
#include"seqlist.h"
void test1()
{
SL s1;
SLInit(&s1);//传地址,传址调用
}
int main()
{
test01();
return 0;
}--这里要注意,使用的是传址调用:将地址传过去,希望对指向的结构体成员进行初始化(修改指向的内容); --如果是传值调用,只是将结构体成员的内容传过去,形参是实参的临时拷贝(二者是独立的个体),改变了形参的值对实参没有影响。(在test.c文件测试会显示使用了未初始化的变量s1,因为是传值,但代码中s1未初始化,无法传值)
--对于上面三文件的相互配合,通过调试来观察之间的数据传递。调试是很好的习惯,尤其是在模块投入复用前,通过调试验证其正确性,就算调试出有错误也能及时修。保证代码质量、提升开发效率、降低后期维护成本的核心必要环节
--经过上面操作,就得到了一个空顺序表,进行应用。
SeqList.c文件
//尾插
void SLPushBack(SL* ps, SLTDataType x)
{
//空间不够
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//增容
SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, newCapacity * sizeof(SLTDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
//空间足够
ps->arr[ps->size++] = x;
}
//输出_函数
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}test.c文件
//实现尾插
void test02()
{
SL s2;//创建结构体变量
SLInit(&s2);//初始化
SLPushBack(&s2, 1);
SLPrint(&s2);
SLPushBack(&s2, 2);
SLPrint(&s2);
SLPushBack(&s2, 3);
SLPrint(&s2);
SLPushBack(&s2, 4);
SLPrint(&s2);
}
int main()
{
test02();
return 0;
}
--在程序调试时,运行到函数时,F11进入到函数内部观察变量的变化。

对于增容操作:一般进行成倍数增加(而不是插一个数就申请一个)——>最好2倍增容——>有效降低增容次数,虽存在空间浪费,但不会太多——>默认插入的数据越多,并表示将来存储的数据也越多(概率学)。

SeqList.c
#include "SeqList.h"
//扩容_函数
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//增容
SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, newCapacity * sizeof(SLTDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//头插_函数
void SLPushFront(SL* ps, SLTDataType x)
{
assert(ps);
//空间不够
SLCheckCapacity(ps);
//空间足够-循环进行移位
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
//空出第一位,插入
ps->arr[0] = x;
ps->size++;//有效数据+1
}--后续会频繁进行空间的判断,所以进行函数封装,直接调用函数。 --其次进行了断言操作,防止传参为空。
test.c文件
#include "SeqList.h"
void test02()
{
SL s2;//创建结构体变量
SLInit(&s2);//初始化
//实现头插
SLPushFront(&s2, 1);
SLPrint(&s2);
SLPushFront(&s2, 2);
SLPrint(&s2);
SLPushFront(&s2, 3);
SLPrint(&s2);
}
int main()
{
test02();
return 0;
}
结语:顺序表通过连续的物理空间实现了高效的随机访问,但其插入删除的代价也显而易见。但这只是开始——下篇我们将深入更关键的删除、查找等核心操作,并完成最终的性能评估。敬请期待。