顺序表:数据结构中的基础线性存储结构
前言:
顺序表功能如图所示
一、顺序表的概念结构及其特点
1.1顺序表的基本概念
顺序表是计算机科学中线性表的两种基本存储结构之一(另一种是链表),它通过一组连续的存储单元来依次存储线性表中的数据元素,使得元素的物理存储位置与逻辑顺序完全一致。
1.2顺序表的结构
1.逻辑结构:在逻辑结构上,顺序表是线性的,就像幼儿园里小朋友们,一个跟着一个排队,有一个小朋友做排头,有一个小朋友做排尾,在中间的小朋友每一个都知道前面的小朋友是谁,在中间的每一个小朋友知道后面的小朋友是谁,就如同用一根线,将其串联起来。
如下图所示:
2.物理结构:在物理结构上,顺序表是基于数组的,一个连续开辟的空间,每个元素的地址是相互紧挨,也如同用一根线一样串联起来,所以在物理结构上也是线性的。
1.3顺序表的特点
特点:
①顺序表具有动态分配空间或则静态开辟空间、支持随机访问和顺序访问,逻辑顺序与物理顺序一致。
②每个元素可以都有唯一的位置,可以通过索引直接访问元素。
③值得注意的是:元素可以是任意类型,包括基本数据类型、结构体等。
二、顺序表的分类
顺序表与数组的关联,顺序表的底层结构是数组,但又不完全是数组,它是对数组基于封装,使其具有了增、删、改、查及其接口的功能,使其具有更加完善的功能适配。
一般来说,我们将顺序表分为两类,一类是静态顺序表,一类是动态顺序表。
①静态顺序表:使⽤定⻓数组存储元素。
对于静态顺序表而言,因为其空间是固定的,我们改给多大空间合适呢,如果空间给小了,导致数据存储不下,引起数据丢失等问题,如果空间给大了,导致空间浪费,程序运行效率降低。
②动态顺序表:使用可更改空间的数组存储元素。
对于动态顺序表而言,因为其空间是可变的,我们可以通过灵活的改变空间,以适应空间不足,或者空间过大的问题。
三、顺序表的实现
这里我们以动态顺序表为例,实现顺序表的各个功能和使用,我们通过三个文件实现顺序表:
①SeqList.h 顺序表函数的声明 及 顺序表结构体的实现
②SeqList.c 顺序表函数的实现
③Test.c 测试顺序表函数
3.1 SeqList.h的准备
头文件需要包含的声明为:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#pragma once
//动态顺序表的实现typedef int SLDataType; //通过将 int 命名为SLDataType 以便后续更改类型 //定义顺序表的结构
typedef struct SeqList
{SLDataType* arr; //动态开辟顺序表int size; //有效元素个数int capacity; //顺序表的容量
}SL;//顺序表初始化
void SLInit(SL* ps);//顺序表销毁
void SLDestroy(SL* ps);//顺序表扩容
void SLCheckCapacity(SL* ps);//顺序表尾插
void SLPushBack(SL* ps, SLDataType x);//顺序表头插
void SLPushFront(SL* ps, SLDataType x);//顺序表尾删
void SLPopBack(SL* ps);//顺序表头删
void SLPopFront(SL* ps);//顺序表指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x);//顺序表指定位置删除
void SLErase(SL* ps, int pos);//顺序表的打印
void SLPrint(SL* ps);//顺序表的查找(按照从前往后的顺序查找,返回第一个元素为x的下标)
int SLFind(SL* ps, SLDataType x);
对于这个头文件我们主要关注 :顺序表的结构体定义
typedef int SLDataType;typedef struct SeqList {SLDataType* arr; //动态开辟顺序表int size; //有效元素个数int capacity; //顺序表的容量 }SL;
①首先定义了一个结构体名为:struct SeqList
②通过typedef将其重命名为 SL
③成员分析:
成员一:SLDataType* arr;
分析:1.定义了一个指针变量,后续开辟动态内存,用该指针进行维护
2.将int类型重命名为SLDataType,方便对其进行改造,只需要将int进行改变,可以将其变为char 、 struct等类型
3.如图所示用arr维护动态内存开辟的空间,可以将动态内存开辟的空间视作数组。
成员二:int size
分析:1.记录数组中存放的有效元素个数。
成员三:int capacity
分析:1.动态内存开辟的空间总大小,也可以认为数组的长度。
通过声明好头文件后,接下来,博主将用图解+代码的方式,对如上的函数进行逐个实现。
3.2 SeqList.c的实现
①顺序表初始化
//这里不用SL ps作为参数,因为形参是实参的临时拷贝,改变形参不影响实参
void SLInit(SL* ps)
{assert(ps);ps->arr = NULL; ps->size = 0; //默认顺序表有效个数为0ps->capacity = 0; //默认顺序表空间为0
}
对顺序表初始化的时候,我们需要关注一个重点:初始化函数的参数。
有些帅读者就会向问,若使用void SLInit(SL ps)作为参数会发生什么呢?
答:我们注意到这里是传值传递,对于传值传递 ,形参是实参的临时拷贝,改变形参会不会影响实参呢,显然这里改变形参不会影响实参,那就相当于没有对实参进行初始化,这段代码变成了无效代码。
温馨提示:博主这里将顺序表的默认空间置为0,其实可以用malloc 函数 或者 calloc函数开辟一定大小的内存空间。
②顺序表销毁
//顺序表的销毁
void SLDestroy(SL* ps)
{//由于是动态开辟的,所以要进行free//判断是否为空,如果不为空将其free,并置为空assert(ps);if (ps->arr){//不为空free(ps->arr);//此时ps->arr仍然保存着arr的地址//但我们不能够访问这片空间,此时改指针为野指针,我们需要将其置空处理ps->arr = NULL;}//有效个数清空ps->size = 0;//开辟的空间清0ps->capacity = 0;
}
温馨提示:
①:这里因为我们使用动态内存开辟空间,所以尤其要重视对于内存的释放,如果有帅观众还不太熟悉动态内存开辟空间的话,可以看一下博主写的动态内存开辟的博客。
②:将空间存储的有效个数 和 空间的总大小置空处理。
③顺序表扩容(核心知识)
//顺序表的扩容
void SLCheckCapacity(SL* ps)
{assert(ps);//什么时候应该扩容?空间不够用的时候。//什么时候空间不够用?当有效个数等于空间容量的时候。if (ps->size == ps->capacity){//使用什么进行扩容? realloc调整动态开辟的空间//每次扩多大?根据数学推导,成倍数增加容量是最好的,一般为2~3倍最佳。//realloc(ps->arr,ps->capacity * 2 *sizeof(SLDataType) );//直接传入ps->capacity这样是正确的吗?//因为我们初始化为0,所以我们需要进行额外处理。int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; //若刚开始为0,则开辟4个空间SLDataType* tmp=(SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType)); //防止空间开辟失败if (tmp == NULL){perror(tmp);exit(1);}//开辟成功,用ps->arr进行维护ps->arr = tmp;//将临时指针置为空ps->tmp=NULL;//更新当前空间ps->capacity = newCapacity;}
}
对于顺序表扩容,我们要明白以下知识点:
①:什么时候应该扩容?
答:空间不够用的时候,即当有效个数等于空间容量的时候。
②:使用什么进行扩容?
答:使用realloc进行动态内存开辟,但要单独去判断其是否开辟成功
③:每次扩容多大内存?
答:根据数据推导,一般来说,成倍数开辟空间,2~3倍是最佳。
代码解析:
①:
if (ps->size == ps->capacity)
这里通过判断是否容量不够,如果size==capacity的话说明不在能够添加数据,要进行扩容处理。
②:
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
这里读者因为初始化的时候,开辟0个大小的空间,所以运用三目运算符进行判断,如果是0(即第一次开辟空间)就对其开辟四个大小的空间,后续成倍增加空间,保证了空间足够且不过于浪费。
③:
SLDataType* tmp=(SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType)); //防止空间开辟失败if (tmp == NULL){perror(tmp);exit(1);}//开辟成功,用ps->arr进行维护ps->arr = tmp;//将临时指针置为空tmp=NULL;//更新当前空间ps->capacity = newCapacity;}
这里使用动态内存开辟空间 ,先赋值给临时指针tmp,判断是否内存开辟成功,如果开辟失败则退出程序,反之赋值给原来的指针arr进行维护,最后别忘记更新当前空间的值。
④顺序表尾插
//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{assert(ps);//判断是否空间足够SLCheckCapacity(ps);ps->arr[ps->size++] = x;}
温馨提示:
1.当进行插入数据的时候优先要判断是否空间足够大,如果空间不够要进行扩容处理。
2.对于尾部插入一个元素的示意图:
3.别忘记对size进行加1操作
⑤顺序表头插
//顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{assert(ps);//判断空间是否足够SLCheckCapacity(ps);for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1]; //根据最后一个元素移动,确定判断条件 arr[1]=arr[0]}//移动结束后,将元素插入第一个位置,即下标为0处。ps->arr[0] = x;ps->size++;
}
温馨提示:
1.当进行插入数据的时候优先要判断是否空间足够大,如果空间不够要进行扩容处理。
2.对于头部插入一个元素的示意图:
3.别忘记对size进行加1操作
⑥顺序表尾删
//顺序表的尾删
void SLPopBack(SL* ps)
{assert(ps);//判断是否可以进行删除assert(ps->size > 0);//将size个数减少一个即可,删除的元素可以被覆盖ps->size--;
}
温馨提示:
1.当进行删除元素的时候,要优先判断是否在顺序表中可以进行删除元素。
2.实际上对size--操作即可,size当前位置的元素可以后续被进行覆盖。
3.对于尾部删除一个元素的示意图:
⑦顺序表头删
void SLPopFront(SL* ps)
{assert(ps);//判断是否可以进行删除assert(ps->size > 0);for (int i = 0; i<ps->size-1; i++){ps->arr[i] = ps->arr[i + 1]; //根据最后一个元素移动,确定判断条件 arr[ps->size-2]=arr[ps->size-1]}//删除一个元素,ps->size的有效个数要减少ps->size--;
}
温馨提示:
1.当进行删除元素的时候,要优先判断是否在顺序表中可以进行删除元素。
2.头删的示意图如下所示:
3.别忘记对size进行减1操作。
⑧顺序表指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);//判断pos是否在有效的位置(size下标可以插入一个元素)assert(pos >= 0 && pos <= ps->size);//判断空间是否足够SLCheckCapacity(ps);for (int i = ps->size; i>pos; i--){ps->arr[i] = ps->arr[i - 1]; //根据最后一个元素移动,确定判断条件 arr[pos+1]=arr[pos];}ps->arr[pos] = x;//增添一个元素,有效个数要增加1ps->size++;
}
温馨提示:
1.判断待插入元素的位置是否合理
2.当进行插入数据的时候优先要判断是否空间足够大,如果空间不够要进行扩容处理。
3.指定位置插入数据的示意图:
![]()
4.别忘记对size进行加1操作。
⑨顺序表指定位置删除
void SLErase(SL* ps, int pos)
{assert(ps);//判断pos是否在有效的位置 (size下标没有元素可删除)assert(pos >= 0 && pos < ps->size);assert(ps->size>0);for (int i = pos; i<ps->size-1; i++){ps->arr[i] = ps->arr[i + 1]; //根据最后一个元素移动,确定判断条件 arr[size-2]=arr[size-1]}//删除一个元素,有效个数要减少一个ps->size--;
}
温馨提示:
1.判断删除的位置是否有效
2.判断是否可以进行删除元素
3.指定位置删除元素的示意图:
4.最后别忘记进行size减1操作。
⑩顺序表的打印
//顺序表的打印
void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->arr[i]);}
}
⑪顺序表的查找
//顺序表的查找(按照从前往后的顺序查找,返回第一个元素为x的下标)
int SLFind(SL* ps, SLDataType x)
{for (int i = 0; i < ps->size; i++){if (ps->arr[i] == x){printf("找到了!");return i;}}//未查找到printf("顺序表中没有该元素\n");return -1;
}
温馨提示:
①通过顺序查找,返回查找到的第一个元素的下标。
②如果没有查找到该元素,则返回-1
3.3 Test.c的测试
针对上面11条函数进行了测试,博主建议大家写完一条测试一条,更能有效率的保证代码正确。
#include"SeqList.h"
void test1()
{SL s;SLInit(&s); SLDestroy(&s);SLCheckCapacity(&s);for (int i = 1; i <= 5; i++){SLPushBack(&s, i);}SLPrint(&s);for (int i = 1; i <= 5; i++){SLPushFront(&s, i);}printf("\n");SLPrint(&s);for (int i = 1; i <= 5; i++){SLPopFront(&s);}printf("\n");SLPrint(&s);SLInsert(&s, 0, 99);SLInsert(&s, 2, 88);SLInsert(&s, s.size, 77);printf("\n");SLPrint(&s);SLErase(&s, 0);SLErase(&s, 2);SLErase(&s, s.size - 1);printf("\n");SLPrint(&s);printf("\n");int ret=SLFind(&s, 1);printf("下标为:%d \n", ret);ret = SLFind(&s, 88);printf("下标为:%d \n", ret);ret = SLFind(&s, 0);printf("下标为:%d \n", ret);
}int main()
{test1();
}
四、完整的代码
4.1SeqList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#pragma once
//动态顺序表的实现typedef int SLDataType; //通过将 int 命名为SLDataType 以便后续更改类型 //定义顺序表的结构
typedef struct SeqList
{SLDataType* arr; //动态开辟顺序表int size; //有效元素个数int capacity; //顺序表的容量
}SL;//顺序表初始化
void SLInit(SL* ps);//顺序表销毁
void SLDestroy(SL* ps);//顺序表扩容
void SLCheckCapacity(SL* ps);//顺序表尾插
void SLPushBack(SL* ps, SLDataType x);//顺序表头插
void SLPushFront(SL* ps, SLDataType x);//顺序表尾删
void SLPopBack(SL* ps);//顺序表头删
void SLPopFront(SL* ps);//顺序表指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x);//顺序表指定位置删除
void SLErase(SL* ps, int pos);//顺序表的打印
void SLPrint(SL* ps);//顺序表的查找(按照从前往后的顺序查找,返回第一个元素为x的下标)
int SLFind(SL* ps, SLDataType x);
4.2SeqList.c
#include"SeqList.h"//顺序表的初始化//这里不用SL ps作为参数,因为形参是实参的临时拷贝,改变形参不影响实参
void SLInit(SL* ps)
{assert(ps);ps->arr = NULL; ps->size = 0; //默认顺序表有效个数为0ps->capacity = 0; //默认顺序表空间为0
}//顺序表的销毁
void SLDestroy(SL* ps)
{//由于是动态开辟的,所以要进行free//判断是否为空,如果不为空将其free,并置为空assert(ps);if (ps->arr){//不为空free(ps->arr);//此时ps->arr仍然保存着arr的地址//但我们不能够访问这片空间,此时改指针为野指针,我们需要将其置空处理ps->arr = NULL;}//有效个数清空ps->size = 0;//开辟的空间清0ps->capacity = 0;
}//顺序表的扩容
void SLCheckCapacity(SL* ps)
{assert(ps);//什么时候应该扩容?空间不够用的时候。//什么时候空间不够用?当有效个数等于空间容量的时候。if (ps->size == ps->capacity){//使用什么进行扩容? realloc调整动态开辟的空间//每次扩多大?根据数学推导,成倍数增加容量是最好的,一般为2~3倍最佳。//realloc(ps->arr,ps->capacity * 2 *sizeof(SLDataType) );//直接传入ps->capacity这样是正确的吗?//因为我们初始化为0,所以我们需要进行额外处理。int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; //若刚开始为0,则开辟4个空间SLDataType* tmp=(SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType)); //防止空间开辟失败if (tmp == NULL){perror(tmp);exit(1);}//开辟成功,用ps->arr进行维护ps->arr = tmp;//对临时变量进行置空tmp=NULL;//更新当前空间ps->capacity = newCapacity;}
}//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{assert(ps);//判断是否空间足够SLCheckCapacity(ps);ps->arr[ps->size++] = x;}//顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{assert(ps);//判断空间是否足够SLCheckCapacity(ps);for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1]; //根据最后一个元素移动,确定判断条件 arr[1]=arr[0]}//移动结束后,将元素插入第一个位置,即下标为0处。ps->arr[0] = x;ps->size++;
}//顺序表的尾删
void SLPopBack(SL* ps)
{assert(ps);//判断是否可以进行删除assert(ps->size > 0);//将size个数减少一个即可,删除的元素可以被覆盖ps->size--;
}void SLPopFront(SL* ps)
{assert(ps);//判断是否可以进行删除assert(ps->size > 0);for (int i = 0; i<ps->size-1; i++){ps->arr[i] = ps->arr[i + 1]; //根据最后一个元素移动,确定判断条件 arr[ps->size-2]=arr[ps->size-1]}//删除一个元素,ps->size的有效个数要减少ps->size--;
}void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);//判断pos是否在有效的位置(size下标可以插入一个元素)assert(pos >= 0 && pos <= ps->size);//判断空间是否足够SLCheckCapacity(ps);for (int i = ps->size; i>pos; i--){ps->arr[i] = ps->arr[i - 1]; //根据最后一个元素移动,确定判断条件 arr[pos+1]=arr[pos];}ps->arr[pos] = x;//增添一个元素,有效个数要增加1ps->size++;
}void SLErase(SL* ps, int pos)
{assert(ps);//判断pos是否在有效的位置 (size下标没有元素可删除)assert(pos >= 0 && pos < ps->size);assert(ps->size > 0);for (int i = pos; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1]; //根据最后一个元素移动,确定判断条件 arr[size-2]=arr[size-1]}//删除一个元素,有效个数要减少一个ps->size--;
}//顺序表的打印
void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->arr[i]);}
}//顺序表的查找(按照从前往后的顺序查找,返回第一个元素为x的下标)
int SLFind(SL* ps, SLDataType x)
{for (int i = 0; i < ps->size; i++){if (ps->arr[i] == x){printf("找到了!");return i;}}//未查找到printf("顺序表中没有该元素\n");return -1;
}
既然看到这里了,不妨点赞+收藏,感谢大家,若有问题请指正。