目录
1.线性表(顺序表是线性表的一种)
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表的实现
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存 储。在数组上完成数据的增删查改。我们今天使用的是动态顺序表:使用动态开辟的数组存储(根据需要动态的分配空间大小)
SLachieve.c | 函数实现 |
SLachieve.h | 函数声明 |
test.c | 函数测试 |
写代码之前,我们需要多文件编译更好实现顺序表,同时为了代码的健壮性,我们应该做到:
1.多使用assert
2.检查顺序表边界
3.写完一个函数通过实例测试是否存在bug,便于修改
2.2 顺序表结构体
typedef int SLDataType;//便于直接修改参数类型
typedef struct SeqList
{
SLDataType* s1;//指向动态数组指针
size_t sz;//数据个数
size_t capacity;//容量
}SL;
2.3 接口函数
void SeqListInit(SL* ps);//初始化顺序表
void SeqListDestory(SL* ps);//删除顺序表
void PrintSL(SL* ps);
void SeqListPushBack(SL* ps, SLDataType x);//从顺序表尾部插入数据
void SeqListPopBack(SL* ps);//从顺序表尾部删除数据
void SeqListPushFront(SL* ps, SLDataType x);//从顺序表头部插入数据
void SeqListPopFront(SL* ps);//从顺序表头部删除数据
int SeqListFind(SL* ps, SLDataType x);// 顺序表查找
void SeqListInsert(SL* ps, size_t pos, SLDataType x);// 顺序表在pos位置插入x
void SeqListErase(SL* ps, size_t pos);// 顺序表删除pos位置的值
2.4 扩容函数实现
由于我们是动态内存开辟空间,需要使用realloc开辟空间(realloc在开辟空间时,如果该指针为空,则其作用跟malloc类似)
注意事项:
1.应该使用三目操作符判断此时容量是否为空,为空容量则开辟成4,再使用realloc开辟动态内存
2.每次扩容大小为原来的两倍,同时扩容时应该判断是否成功开辟
3.把开辟好的地址赋值给顺序表指针
void CheckCapitcy(SL* ps)
{
assert(ps);
if (ps->capacity == ps->sz)
{
int NewCapitcy = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->s1, NewCapitcy * sizeof(SLDataType));
if (tmp == NULL)
{
perror("SLPushBack:realloc Error");
return;
}
ps->s1 = tmp;
ps->capacity = NewCapitcy;
}
}
2.5 初始化顺序表
注意事项:
1.在test.c中应初始化顺序表,通过SL 创建s1变量
void SeqListInit(SL* ps)
{
ps->s1 = NULL;
ps->capacity = ps->sz = 0;
}
2.6 从顺序表尾部插入数据
注意事项:
1.使用时应该检查容量
2.数据个数随着插入也要增加
void SeqListPushBack(SL* ps, SLDataType x)//从顺序表尾部插入数据
{
assert(ps);
CheckCapitcy(ps);
ps->s1[ps->sz]= x;
ps->sz++;
}
2.7 从顺序表尾部删除数据
注意事项:
1.数据个数--即可,变相等于删除(增加时覆盖原数据)
void SeqListPopBack(SL* ps)//从顺序表尾部删除数据
{
assert(ps);
if (ps->sz > 0)
{
ps->sz--;
}
}
2.8 从顺序表头部插入数据
注意事项:
1.使用时应该检查容量
2.如果顺序表有数据,从后往前覆盖数据,把新数据放在第一位,同时数据个数++
void SeqListPushFront(SL* ps, SLDataType x)//从顺序表头部插入数据
{
assert(ps);
CheckCapitcy(ps);
for (int i = ps->sz; i > 0; i--)
{
ps->s1[i] = ps->s1[i - 1];
}
ps->s1[0] = x;
ps->sz++;
}
2.9 从顺序表头部删除数据
注意事项:
1.直接覆盖原数据即可,同时数据个数--
void SeqListPopFront(SL* ps)//从顺序表头部删除数据
{
assert(ps);
if (ps->sz > 0)
{
for (int i = 0; i < (ps->sz); i++)
{
ps->s1[i] = ps->s1[i + 1];
}
}
ps->sz--;
}
2.10 顺序表查找,返回下标
注意事项:
1.找到后返回下标位置
int SeqListFind(SL* ps,SLDataType x)// 顺序表查找,返回下标
{
assert(ps);
for (int i = 0; i < (ps->sz); i++)
{
if (ps->s1[i] == x )
{
return i;
}
}
return -1;
}
2.11 顺序表在pos位置插入x
注意事项:
1.pos的插入位置一定得小于数据个数,否则违反了顺序表连续存放的规则
2.检查容量
3.数据往后挪动,留出空间插入x,同时数据个数++
void SeqListInsert(SL* ps, size_t pos, SLDataType x)//顺序表在pos位置插入x
{
assert(ps);
assert(ps->sz >= pos); //被插入的位置必须小于等于sz
CheckCapitcy(ps);
for (int i = ps->sz; i > pos; i--)
{
ps->s1[i] = ps->s1[i - 1];
}
ps->s1[pos] = x;
ps->sz++;
}
2.12 顺序表删除pos位置的值
注意事项:
1.必须存在元素可删除
2.数据覆盖,便等同于删除
void SeqListErase(SL* ps, size_t pos)// 顺序表删除pos位置的值
{
assert(ps->sz >0);//有元素可删除
for (int i = pos; i<ps->sz; i++)
{
ps->s1[i] = ps->s1[i + 1];
}
ps->sz--;
}
2.13 销毁顺序表
注意事项:
1.动态开辟的内存一定要free释放
2. 把指针置为NULL,防止野指针
void SeqListDestory(SL* ps)//删除顺序表
{
assert(ps);
free(ps->s1);
ps->s1 = NULL;
ps->sz = ps->capacity = 0;
}
3.原码链接
4.顺序表问题
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
于是我们便引入了链表的概念(下篇博客)