目录
顺序表的概念和结构
顺序表是数据结构中线性表的一种。
用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
像之前的通讯录,其本质上就是顺序表。
那么很显然,顺序表相应的就有动态和静态两个版本。
其中静态版本中数组大小固定,容易发生空间不足或空间冗余。
所以,下面主要介绍动态顺序表各个接口功能的实现。
顺序表各个接口的实现
创建顺序表
由于我们要知道顺序表实时大小和实时容量,所以创建一个结构体变量能同时存储三个数据:
//重定义顺序表存储的数据类型,方便后续维护
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data;//指向后续动态内存分配的数据块的首地址
int size; //存储当前顺序表中的数据数量
int capacity; //存储当前顺序表的最大容量
}SeqList;
初始化顺序表
这一步我们需要创建顺序表并对其进行初始化,
将其 size 置零,capaticy 置为传入参数。
如果想把顺序表中每个数据都初始化为 0 的话可以使用 calloc 函数。
//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity);
void SeqListInit(SeqList* ps, size_t capacity)
{
ps->data = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
if (ps->data == NULL)
{
perror("SeqListInit");
return;
}
ps->size = 0;
ps->capacity = capacity;
}
顺序表打印
打印顺序表中的数据,
因为顺序表中只有 size 个数据,
所以只需要循环 size 次打印。
void SeqListPrint(SeqList* ps);
void SeqListPrint(SeqList* ps)
{
assert(ps->size > 0);
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->data[i]);
printf("\n");
}
检查空间,如果满了,进行增容
后面无论是要尾插、头插还是中间插,
都有可能出现顺序表满的情况,
所以在此之前要进行空间检查,
如果空间存满了就需要扩容。
这里每次将容量扩大为原来的 2 倍,
需要使用 realloc 函数。
扩容之后不要忘了重置 capacity 。
void CheckCapacity(SeqList* ps);
void CheckCapacity(SeqList* ps)
{
SLDataType* tmp = NULL;
if (ps->capacity == ps->size)
tmp = realloc(ps->data, ps->capacity * 2);
if (tmp != NULL)
ps->data = tmp;
else
return;
ps->capacity *= 2;
}
顺序表尾插
插入数据之前先判断是否需要扩容。
在顺序表的最后插入一个数据 x ,
由于 size 的值就是现有数据中最后一个数据的下标 +1,
所以直接给 size 位置处的数据赋值。
尾插之后记得 size++ 。
void SeqListPushBack(SeqList* ps, SLDataType x);
void SeqListPushBack(SeqList* ps, SLDataType x)
{
CheckCapacity(ps);
ps->data[ps->size] = x;
ps->size++;
}
顺序表尾删
尾删,并不需要我们操作现有的最后一个数据。
因为我们看到的数据是前 size 个,
所以只需要改变 size 就可以减少呈现在我们眼前的数据。
void SeqListPopBack(SeqList* ps);
void SeqListPopBack(SeqList* ps)
{
assert(ps->size > 0);
ps->size--;
}
顺序表头插
插入数据之前先判断是否需要扩容。
头插的话后面的数据就要往后移,
从后往前来即可:
然后将空出的位置赋成我们想要的 x 即可。
最后将 size++ 。
void SeqListPushFront(SeqList* ps, SLDataType x);
void SeqListPushFront(SeqList* ps, SLDataType x)
{
CheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->data[end + 1] = ps->data[end];
end--;
}
ps->data[0] = x;
ps->size++;
}
顺序表头删
头删只需要把头上的元素覆盖掉,
覆盖顺序如下:
最后别忘了 size– 。
void SeqListPopFront(SeqList* ps);
void SeqListPopFront(SeqList* ps)
{
assert(ps->size > 0);
int start = 1;
while (start < ps->size)
{
ps->data[start - 1] = ps->data[start];
start++;
}
ps->size--;
}
顺序表查找
我们还需要实现在指定位置处插入或删除元素,
这就需要知道指定位置是哪个位置,
所以在此之前先完成查找接口。
只需遍历一遍顺序表,
这里返回的是指定元素第一次出现的位置。
int SeqListFind(SeqList* ps, SLDataType x);
int SeqListFind(SeqList* ps, SLDataType x)
{
for (int i = 0; i < ps->size; ++i)
if (ps->data[i] == x)
return i;
return -1;
}
删除指定位置的值
删除指定位置的值,
只需要将指定位置处的数据覆盖掉。
覆盖方式为从前往后走:
最后记得 size– 。
void SeqListErase(SeqList* ps, size_t pos);
void SeqListErase(SeqList* ps, size_t pos)
{
assert(pos < ps->size);
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
在指定位置插入
插入数据还是要先判断是否需要扩容。
首先将 pos 处及其后部元素向后挪一位,
然后把 pos 处元素赋成 x :
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(pos <= ps->size);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
顺序表销毁
由于顺序表是动态开辟来的,
所以程序结束时一定要将空间释放,
以免造成内存泄漏。
void SeqListDestory(SeqList* ps);
void SeqListDestory(SeqList* ps)
{
free(ps->data);
ps->data = NULL;
ps->capacity = 0;
ps->size = 0;
}
总结
对于顺序表,很明显它是连续存放数据的,且支持随机访问,可以直接访问任意位置处的数据。
但是,每次头插或指定位置插入、删除元素都需要遍历数组,时间复杂度为 O(N),效率较低。
此外,这里动态增容一次增加原来的一倍,当数据量很大的时候,增容占用的空间也会更多,而如果剩余数据很少,那同样会造成大量空间的浪费。
完整代码
SeqList.h
将有关变量、符号及函数的声明、头文件的引用放在头文件中:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data;
int size;
int capacity;
}SeqList;
//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity);
// 顺序表打印
void SeqListPrint(SeqList* ps);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps);
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* ps);
// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* ps);
Seqlist.c
相关接口函数的实现放在源文件 Seqlist.c 中:
#include "SeqList.h"
//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity)
{
ps->data = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
if (ps->data == NULL)
{
perror("eqListInit");
return 0;
}
ps->size = 0;
ps->capacity = capacity;
}
// 顺序表打印
void SeqListPrint(SeqList* ps)
{
assert(ps->size > 0);
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->data[i]);
printf("\n");
}
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps)
{
SLDataType* tmp = NULL;
if (ps->capacity == ps->size)
tmp = realloc(ps->data, ps->capacity * 2);
if (tmp != NULL)
ps->data = tmp;
ps->capacity *= 2;
}
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
CheckCapacity(ps);
ps->data[ps->size] = x;
ps->size++;
}
// 顺序表尾删
void SeqListPopBack(SeqList* ps)
{
assert(ps->size > 0);
ps->size--;
}
// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
CheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->data[end + 1] = ps->data[end];
end--;
}
ps->data[0] = x;
ps->size++;
}
// 顺序表头删
void SeqListPopFront(SeqList* ps)
{
assert(ps->size > 0);
int start = 1;
while (start < ps->size)
{
ps->data[start - 1] = ps->data[start];
start++;
}
ps->size--;
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x)
{
for (int i = 0; i < ps->size; ++i)
{
if (ps->data[i] == x)
{
return i;
}
}
return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(pos <= ps->size);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
assert(pos < ps->size);
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
// 顺序表销毁
void SeqListDestory(SeqList* ps)
{
free(ps->data);
ps->data = NULL;
ps->capacity = 0;
ps->size = 0;
}