一. 什么是顺序表
顺序表本质上为数组,但是,在数组的基础上,顺序表要求数据是连续存储的,不能跳跃间隔。顺序表分分为静态顺序表和动态顺序表两种。
1.1 静态顺序表
静态顺序表的容量是固定不变的,当存储的数据量达到上限时,无法进行扩容。
静态顺序表的定义:
typedef struct SeqList
{
int a[1000]; //存储数据的数组
int size; //当前存储数据的个数
}SL;
该静态顺序表最多存储1000个整型数据
静态顺序表的灵活性较差,很难判断应该开辟多大的内存空间,容易造成空间不足或浪费。
1.2 动态顺序表
动态顺序表在存储数据达到容量上限时,可以进行扩容,可以有效避免静态顺序表中易出现的内存空间开辟不足的情况。
动态顺序表的定义:
typedef struct SeqList
{
int* a; //指向为存储数据而动态开辟的内存空间
int size; //动态顺序表中已经存储的数据的个数
int capacity; //该动态顺序表当前存储数据的上限
}
![](https://img-blog.csdnimg.cn/bd5d1f436ee7407a882c590f57351a37.png)
二. 顺序表接口函数
由于实际工程应用中多采用动态顺序表,因此,本文以动态顺序表为例,对顺序表接口函数进行讲解(以1.2中定义的动态顺序表为例)。
注:演示代码中DataType全部表示为int(在头文件中使用了#define进行重定义)
2.1 顺序表初始化函数SeqListInit
假设要将顺序表中的int* a初始化为NULL,将当前存储数据量size和顺序表容量capacity均初始化为0,具体实现见演示代码2.1。
演示代码2.1:
//初始化顺序表函数
void SeqListInit(SL* ps)
{
ps->a = NULL; //数据存储空间
ps->size = 0; //初始状态没有存储数据
ps->capacity = 0; //初始状态顺序表容量为0
}
2.2 尾插数据函数SeqListBackPush
尾插数据函数实现的功能是将一个特定的数据x插到顺序表的尾部,SeqListBackPush函数首先调用容量检查函数SeqListCheckCapacity,判断顺序表是否有剩余的空间可以容纳数据x,如果没有剩余空间,则调用realloc函数扩大顺序表的空间。在确保顺序表获得足够空间后,首先使用ps->a[ps->size] = x语句在尾部插入x,然后使用ps->size++语句来记录顺序表中数据量+1。具体实现见演示代码2.2。
演示代码2.2:
//容量检查函数
void SeqListCheckCapacity(SL* ps)
{
if (ps->capacity == 0 || ps->capacity == ps->size)
{
int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity); //新开辟的空间容量
DataType* tmp = (DataType*)realloc(ps->a, newcapacity * sizeof(DataType));
if (NULL == tmp) //检验是否开辟成功
{
perror("realloc");
exit(-1);
}
ps->a = tmp; //ps->a指向新的空间
ps->capacity = newcapacity; //容量更新
}
}
//尾插函数
void SeqListBackPush(SL* ps, DataType x)
{
//检验顺序表存储空间是否已满
SeqListCheckCapacity(ps); //检验函数,若满了就扩容
ps->a[ps->size] = x; //在顺序表尾部插入数据
ps->size++; //数据量+1
}
2.3 尾删数据函数SeqListBackPop
尾删函数实现的功能是删除顺序表尾部的一个数据。只需要顺序表中数据个数size减一,即可得到与在尾部删除一个数据相同的效果。在删除数据前应判断顺序表中是否存在数据,ps->size==0表示顺序表中无数据,不进行任何操作。尾删函数的具体实现见演示代码2.3。
演示代码2.3:
//尾删函数
void SeqListBackPop(SL* ps)
{
//如果顺序表中有数据,就将数据量-1,效果等同于尾删
//如果顺序表中没有数据,不进行任何操作
if (ps->size > 0)
{
ps->size--;
}
}
2.4 头插数据函数SeqListFrontPush
首先检查顺序表是否有剩余空间,没剩余空间就扩容。要在头部插入数据,首先应当把顺序表中现存的数据均向后移动一个单位(向后移动数据应先从顺序表尾部的数据开始,若先移动顺序表起始位置的数据,则会造成数据在移动之前被覆盖的问题),移动完数据后,将ps->a[0]赋值为x,ps->size加一。头插函数的具体实现见演示代码2.4。
演示代码2.4:
void SeqListFrontPush(SL* ps, DataType x)
{
SeqListCheckCapacity(ps); //检验顺序表存储空间是否已满
//顺序表中已有数据向后移动
int end = ps->size; //尾部数据位置
while (end--)
{
ps->a[end + 1] = ps->a[end];
}
ps->a[0] = x; //在头部插入数据x
ps->size++; //容量扩大
}
2.5 头删数据函数SeqListFrontPop
头删数据函数实现的功能是删除顺序表中的第一个元素。首先判断顺序表中是否存在数据,若存在,再分为两种情况讨论:
- 顺序表中只有一个数据,直接使用ps->size--删除数据
- 顺序表中含有两个及以上数据,首先将第二个数据往后的每个数据向前移动一个单位,然后用ps->size-- 指令完成头删操作。
头删数据函数的具体实现见演示代码2.5。
演示代码2.5:
void SeqListFrontPop(SL* ps)
{
//检查顺序表中是否存在数据
//不存在数据则不执行任何操作
if (ps->size > 0)
{
//如果顺序表中只有一个数据,则直接将其删除
if (ps->size == 1)
{
ps->size--;
}
else
{
//有两个及以上数据,从前往后移动数据,数据量-1
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
}
}
2.6 数据查找函数SeqListFind
依次遍历顺序表中每个数据,与待查找的数据x进行比较,发现与x相同的值就打印这是顺序表的第几个数据,退出函数。若遍历完顺序表中所有数据还没找到x,就打印找不到。详见演示代码2.6。
演示代码2.6:
void SeqListFind(SL* ps, DataType x)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
printf("找到了,%d位于顺序表的第%d个位置\n", x, i + 1);
return;
}
}
printf("找不到\n");
}
2.7 在指定位置插入数据函数SeqListInsert
该函数有三个参数,其中pos为插入数据的位置,x为插入的值。函数首先判断pos是否超出范围,若超出范围就报错,函数终止执行。当确定pos为超出范围后,先检查容量,顺序表中若无剩余空间就扩容,再将第pos个数据开始的每个数据向后移动一个单位,最后将第pos个数据重新赋值为x,数据量ps->size加一。具体实现见演示代码2.7。
演示代码2.7:
void SeqListInsert(SL* ps, int pos, DataType x)
{
//检验插入位置是否超出范围
//若超出范围,则提示错误信息,退出函数
if (pos <= 0 || pos > ps->size)
{
printf("插入数据的位置越界!\n");
return;
}
//检验容量是否足够
SeqListCheckCapacity(ps);
//从pos位置开始,每个数据向后移动一位
int end = ps->size;
while (end-- >= pos)
{
ps->a[end + 1] = ps->a[end];
}
ps->a[pos - 1] = x;
ps->size++;
}
2.8 删除指定位置数据函数SeqListErase
该函数首先判断pos是否超出范围,在确定pos在合法范围内后,将pos之后的数据全都向前移动一位即可。详见演示代码2.8。
演示代码2.8:
void SeqListErase(SL* ps, int pos)
{
//pos位置往后的数据全部向前移动一个单位
int begin = pos;
for (begin = pos; begin < ps->size; begin++)
{
ps->a[begin - 1] = ps->a[begin];
}
ps->size--;
}
2.9 顺序表内容打印函数SeqListPrint
依次遍历顺序表中的每个数据,使用printf进行打印即可,详见演示代码2.9。
演示代码2.9:
void SeqListPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
2.10 释放动态内存函数SeqListDestory
该函数在顺序表使用全部结束后调用,该函数实现的功能有:释放动态内存、将顺序表中包含数据个数size和顺序表容量capacity均置零。详见演示代码2.10。
演示代码2.10:
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL; //释放动态开辟的内存空间
ps->size = ps->capacity = 0; //顺序表的内容量和容量均清零
}
三. 顺序表的缺陷
- 顺序表在空间不足时需要扩容,而扩容需要付出一定的代价。
- 为了避免频繁扩容,我们在顺序表无剩余空间时一般都是扩容2倍,这可能导致空间浪费。
- 顺序表要求数据从开始位置连续存储,那么我们在头部或者中间部分插入数据、删除数据时,就需要大量挪动数据,效率不高。
链表就是针对线性表的缺陷而设计的,我的下篇博客会对链表进行详解。
全文结束,感谢大家的阅读,敬请批评指正。