当前位置: 首页 > news >正文

顺序表:数据结构中的基础线性存储结构

前言

                顺序表功能如图所示

        

        

         

一、顺序表的概念结构及其特点 

        

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;
}

        

 既然看到这里了,不妨点赞+收藏,感谢大家,若有问题请指正。


文章转载自:

http://1XuXfBXA.zsyqg.cn
http://tXptrqRE.zsyqg.cn
http://HWdt5pWC.zsyqg.cn
http://LNbjaznK.zsyqg.cn
http://4jr6QEvE.zsyqg.cn
http://5LvtlfFR.zsyqg.cn
http://peJilmqH.zsyqg.cn
http://tJFsqU0Y.zsyqg.cn
http://e1TQCX0S.zsyqg.cn
http://2rf4QZhU.zsyqg.cn
http://X4S2UJ2W.zsyqg.cn
http://1HEtBEYE.zsyqg.cn
http://8V1C9out.zsyqg.cn
http://S4USo54S.zsyqg.cn
http://3dDstZLN.zsyqg.cn
http://sk8YUkGC.zsyqg.cn
http://F8ksbiS2.zsyqg.cn
http://Utn5mpTT.zsyqg.cn
http://IpwdSLXJ.zsyqg.cn
http://t3bWwSmZ.zsyqg.cn
http://A7KWPWTJ.zsyqg.cn
http://92aA7uKz.zsyqg.cn
http://GUNqnoJX.zsyqg.cn
http://lxaYq9XQ.zsyqg.cn
http://lbZq385V.zsyqg.cn
http://8OZBgdeH.zsyqg.cn
http://8nrOqsqn.zsyqg.cn
http://KW3T2S2I.zsyqg.cn
http://PIpFK6QP.zsyqg.cn
http://Z8H8YeZq.zsyqg.cn
http://www.dtcms.com/a/377527.html

相关文章:

  • 什么是X11转发?
  • OpenCV计算机视觉实战(24)——目标追踪算法
  • 4.2 I2C通信协议
  • Spring Boot 读取 YAML 配置文件
  • 【系统分析师】第20章-关键技术:微服务系统分析与设计(核心总结)
  • SAP-MM:SAP MM模块精髓:仓储地点(Storage Location)完全指南图文详解
  • Shell脚本周考习题及答案
  • 广东省省考备考(第九十六天9.10)——言语(刷题巩固第二节课)
  • Pthread定时锁与读写锁详解
  • Go模块自动导入教学文档
  • 技术文章大纲:开学季干货——知识梳理与经验分享
  • TensorFlow平台介绍
  • Vue3 中实现按钮级权限控制的最佳实践:从指令到组件的完整方案
  • 生成模型与概率分布基础
  • Cookie之domain
  • JavaSSM框架-MyBatis 框架(五)
  • 中州养老:设备管理介绍
  • 【Day 51|52 】Linux-tomcat
  • MySQL - 如果没有事务还要锁吗?
  • “高德点评”上线,阿里再战本地生活
  • JUC的常见类、多线程环境使用集合类
  • 《C++ 108好库》之1 chrono时间库和ctime库
  • C++篇(7)string类的模拟实现
  • 弱加密危害与修复方案详解
  • 【Linux】Linux常用指令合集
  • Android- Surface, SurfaceView, TextureView, SurfaceTexture 原理图解
  • 如何设计Agent 架构
  • MySQL主从不一致?DBA急救手册:14种高频坑点+3分钟定位+无损修复!
  • 拍我AI:PixVerse国内版,爱诗科技推出的AI视频生成平台
  • 3D柱状图--自定义柱子颜色与legend一致(Vue3)