顺序表
一、顺序表的基本概念
了解顺序表,首先要了解线性表的概念。
线性表:对于一组拥有N个数据的线性表:除了首元素a0无直接前驱,以及尾元素无直接后继,其它任何一个元素ai,有且仅有一个直接前驱a(i-1),有且只有一个直接后继。
满足这种数学关系的一组数据,其中的数据是一个挨着一个的,我们称之为一对一关系,是线性的。如果数据之间的关系不是一对一的,就是非线性的。
顺序表:顺序存储的线性表。
顺序表就是将数据存储到一片连续的内存中,在C语言环境下,它可以是具名的栈数组,也可以是匿名的堆数组。
顺序表的存储方式不仅仅要提供数据的存储空间,而且必须要能体现数据之间的逻辑的关系,比如,如果甲乙丙依次排队,乙前面是甲,后面是丙,那么存储位置必须按照这样的逻辑把甲存放在乙后面,把丙排在乙后面。
当采用顺序存储的方式存储数据时,唯一能够表达数据本身逻辑关系的就是存储位置。
通俗地说,顺序表的存储是连续的,即必须从首元素的位置开始按顺序往后存储,不能跳过中间的位置往后存储。
注意:上图只是为了让读者更好的理解顺序表而举的一个例子,并不是说其元素也要按照大小顺序有序存放。顺序表并没有要求存放的元素应该是有序的,它存放的元素可以是有序的,也可以是无序的,关于这一点我相信不少初学者都会有疑惑,所以笔者在这里做个特别说明。
二、顺序表的基本操作
顺序表的设计
为方便对顺序表进行管理,需要一个专门管理顺序表的结构体,该结构体中一般包含以下几个成员:
(1)顺序表总容量
(2)顺序表当前尾元素下标
(3)顺序表指针
以下是管理结构体示例代码:
typedef struct SequenceList{
int cap;//顺序表容量,类似于数组的容量
int last;//当前尾元素下标
int *data;//存放顺序表数据,以整型数据为例
}SqList;
SqList *SL = NULL;//定义一个顺序表指针
顺序表的初始化
建立一个空的、不包含元素的顺序表,需要初始化顺序表的容量,尾元素下标,申请分配存储数据的空间,由于一开始顺序表没有元素,尾元素下标置位-1;以下是初始化顺序表的示例代码,以传参的方式初始化顺序表。
//成功返回true,失败返回false
bool SqList_Init(SqList* &SL,int cap)
{
if(SL = (SqList *)malloc(sizeof(SqList)) == NULL)
return false;//malloc分配空间失败,返回false
//内存分配成功
SL->cap = cap;
SL->last = -1;
//cap为顺序表元素最大总数,cap * sizeof(int) 为需要分配的存储空间
if(SL->data = (int *)malloc(cap * sizeof(int)) == NULL)
{//SL->data申请分配空间失败,即顺序表初始化失败,需要释放SL,并返回false
free(SL);
SL = NULL;//防止SL成为野指针
return false;
}
return true;
}
判空
bool SqList_IsEmpty(SqList* SL)
{
if(SL == NULL || (SL->last == -1))
return true;
return false;
}
增删节点
在顺序表中增加一个数据,可以有多种方式,可以在原数组的头部、尾部或者按下标增删数据,也可以按值删除数据。以下分别为三种方式的增删数据示例代码,以及按值删除数据的代码。
//从头部增加
bool SqList_Head_Add(SqList* &SL,int data)
{
if(SL->last == (SL->cap-1))//顺序表已满
return false;
if(SL->last == -1)//顺序表为空
{
SL->data[0] = data;
SL->last++;
return true;
}
//顺序表不为空
int i;
for(i = last;i>=0;i--)//将原有数据全部往后挪一位
{
SL->data[i+1] = SL->data[i];
}
SL->data[0] = data;
SL->last++;
return true;
}
//从头部删除
bool SqList_Head_Dele(SqList* &SL)
{
if(SL->last == -1)//顺序表为空
return false;
//不为空
//1.只有一个元素,直接让尾元素下标自减
if(SL->last == 0)
{
SL->last--;
return true;;
}
//2.两个及两个以上的元素
int i;
for(i = 0;i<SL->last;i++)//直接将原有数据全部往前挪一位
{
SL->data[i] = SL->data[i+1];
}
SL->last--;
return true;
}
//从尾部增加
bool SqList_Tail_Add(SqList* &SL,int data)
{
if(SL->last == (SL->cap-1))//顺序表已满
return false;
SL->last++;
SL->data[last] = data;
return true;
}
//从尾部删除
bool SqList_Tail_Dele(SqList* &SL)
{
if(SL->last == -1)//顺序表为空
return false;
SL->last--;
return true;
}
//从任意位置增加,参数 pos 为指定增加元素的位置
bool SqList_Add(SqList* &SL,int data,int pos)
{
if(SL->last == (SL->cap-1))//顺序表已满
return false;
//插入位置,不能大于顺序表的容量,也不能破坏顺序表的线性关系
if(pos > (SL->cap-1) || pos > (SL->last+1) || pos < 0)
return false;
//在尾元素位置以及尾元素之前的任意位置插入元素
if(pos <= SL->last)
{
int i;
for(i = SL->last;i >= pos;i--)//第pos个元素到尾元素,整体往后移一位
SL->data[i+1] = SL->dara[i];
SL->data[pos] = data;//元素后移之后,在第pos个元素的位置增加新元素
SL->last++;//增加新元素后,尾元素下标加1
return true;
}
//在尾元素之后插入新元素,即 pos = SL->last + 1
if(pos > SL->last)
{
SL->data[pos] = data;//直接在第pos个位置增加新元素
SL->last++;//尾元素加1
return true;
}
}
//从任意位置删除,参数 pos 为删除指定元素的位置
bool SqList_Dele(SqList* &SL,int pos)
{
if(SL->last == -1)//顺序表为空
return false;
//删除元素的位置,不能大于顺序表的容量,也不能破坏顺序表的线性关系
if(pos > (SL->cap-1) || pos > SL->last || pos < 0)
return false;
//在尾元素之前的位置删除元素,不包括尾元素的位置
if(pos < LS->last)
{
int i;
for(i = pos;i < SL->last;i++)//从第pos位开始(包括第pos位),整体往前移动一位
SL->data[i] = SL->data[i+1];
SL->last--;//尾元素下标减1
return true;
}
//删除的元素为尾元素
if(pos == LS->last)
{
SL->last--;//直接让尾元素下标减1
return true;
}
}
//按值删除指定元素
bool SqList_Del_By_Val(SqList* &SL,int val)
{
if(SL->last == -1)//顺序表为空
return false;
int i,cnt = 0,index = -1;
for(i = 0;i <= SL->last;i++)
{
if(SL->data[i] == val)
{
index = i;//记录要删除的值的下标
cnt++;//有可能存在多个相同的val,记录有多少个Val
}
}
if(index == -1)//没有找到值为val的元素
return false;
SqList_Dele(SL,index);//按下标删除指定元素
while(--cnt)//如果有多个相同值,递归调用自己删除值为Val的元素
{
SqList_Del_By_Val(SL,val);
}
return true;
}
销毁顺序表
顺序表不再需要使用的时候,应该释放其所占用的空间,这也叫顺序表的销毁。
void SqList_Destroy(SqList* &SL)
{
if(SL == NULL)//顺序表为空
return;
//不为空
//怎么初始化的,就要怎么释放
free(SL->data);
SL->data = NULL;
free(SL);
SL = NULL;
}
三、顺序表的优缺点
由于顺序存储的逻辑关系是用物理位置来表达的,因此增删数据需要成片的移动数据,这对数据节点的增删操作很不友好。当然,优缺点也有其优点,总结顺序表的特点如下:
优点:
(1)不需要多余的信息来记录数据之间的关系,存储密度高
(2)所有数据顺序存储在一片连续的内存中,支持立即访问任意一个随机数据,可以跟数组一样直接访问第i个节点,如顺序表的第i个节点数据为SL->daata[i]。
缺点:
(1)插入、删除时需要保持数据的线性逻辑关系,需要成片地移动数据
(2)当数据节点较多时,需要移动一大片连续内存空间
(3)当频繁操作顺序表的数据节点时,内存的释放和分配不灵活
四、关于传引用
小伙伴们应该都注意到了,笔者在函数传参时使用了bool SqList_Init(SqList* &SL,int cap)这样形式传递指针参数,初学者可能对此感到困惑,我就不再这里做详细说明了,感兴趣的小伙伴可以到以下链接学习解惑:https://blog.csdn.net/weixin_46637351/article/details/126046567