什么是顺序表
线性表的顺序存储结构是把线性表中的所有元素按照其逻辑顺序依次存储到从计算机存储器中指定存储位置开始的一块连续的存储空间。
顺序表是存储到一块连续的存储空间,逻辑上相邻的两个元素在对应的顺序表中它们的存储位置也是相邻的,那么它们就是映射关系,而且这种映射称为直接映射。
线性表的顺序存储结构简称为顺序表(sequential list)
这样的线性表,其中的第一个元素的存储位置就是指定的存储位置,寻找其对应位置的值很简单只需要将其序号-1,就能得所对应的值。
若线性表的元素类型为ElemType,那么每个元素所占的存储空间大小就为sizeof(ElemType),若整个线性表的长度为n,那么这个线性表所占的存储空间就为:n × sizeof(ElemType)。
MaxSize指的是数组大小,根据所需数组的大小,一般定义为一个整型常量。接下来我们就定义一个线性表,如下:
#define MaxSize 60
typedef struct
{
ElemType data[MaxSize]; //存放线性表中的元素
int length; //存放线性表的长度
}SqList; //顺序表类型
接下来,我便重点介绍几种顺序表的基本运算算法(因为很多东西都是相通的):
1.建立顺序表
这里我们将一个数组的数据传入线性表,这是一个很普遍存在的情况:
即一个数组a,a中有n个元素(n的大小不能超过MaxSize),我们将其中的元素,依次放入顺序表L中去,代码如下:
void CreateList(SqList * &L,ElemType a[],int n) //由a中的n个元素建立顺序表
{
int i=0,k=0; //k表示L中元素的个数,初始值为0
L = (SqList *)malloc(sizeof(SqList)); //分配存放线性表的空间
while(i < n) //i扫描数组a的元素
{
L->data[k]=a[i]; //将元素a[i]存放到L中去
k++;
i++;
}
L->length=k; //设置L的长度k
}
当调用上述算法创建好L所指的顺序表后,需要回传给对应的实参,也就是说L是输出型参数,所以在形参L前面需要加上引用符“&”。
2.按照序号求线性表中的元素
接下来,我介绍基本线性表比较重要的功能,增删查中的查,查,可以从两个角度去查,第一个就是知道序号,去查序号所对应的值;
例如,我们知道序号i,那么我们首页应该去判断,i这个序号是否在表中存在,只有在i存在时,我们才能找到其对应的值,然后返回true,若不存在,我们直接返回一个false,代码如下:
bool GetElem(SqList *L,int i,ElemType &e)
{
if(i<1 || i > L->length)
return false; //参数i错误时返回false
e=L->data[i-1]; //取元素的值
return true; //成功找到元素时返回true
}
时间复杂度为O(1)。
3.按元素值查找
查,另一个角度就是,知道元素的值,去查找对应的逻辑序号;
例如,给出一个值e,查找顺序表中的逻辑序号,若不存在,则返回值为0,代码如下:
int LocateElem(SqList *L,ElemType e)
{
int i=0;
while(i< L->length && L->data[i] !=e)
i++; //查找元素e
if(i>= L->length) //未找到时返回0
return 0;
else
return i+1; //找到后返回其逻辑序号
}
上述代码中有while循环,其循环中的i++语句,其平均执行(n+)/2次,时间复杂度为O(n)。
4.插入数据元素
接下来讲述,增删查中的增;
那么顺序表的本质是一个数组,数组插入数据,说实话,其实是比较麻烦的,下面就举个例子:
在顺序表L中,第i个位置,插入一个新的元素e,首先我们得判断 i 它是否正确,是否在这个给顺序表中,若不正确,我们返回一个false;
若 i 正确,那么我们就得在其位置插入元素e,将 i 后面得元素依次往后面挪一个位置,如下图,从最后一个元素开始移动,如果从 i 后面的元素往后移,那么结果是显而易见的,后面的所有值都会变成同一个值。
代码,如下:
bool ListInsert(SqList *L,int i,ElemType e)
{
int j;
if(i<1 || i> L->length+1 || L->length == MaxSize)
return false; //参数i错误是返回false
i--; //将顺序表的逻辑序号转化为物理序号
for(j=L->length;j>i;j--) //将data[i]及后面的元素后移一个位置
L->data[j]=L->data[j-1];
L->data[i]=e; //插入元素e
L->length++; //顺序表的长度增1
return true; //成功插入返回true
}
平均时间复杂度为O(n)。
5.删除数据元素
其实通过上述的插入数据,可以大致联想出,删除元素的方法;
若在序号为 i 处删除元素,首先我们还是得判断 i 是否合理,若 i 不合理不正确,那么我们就只需要直接返回false,若正确,我们就应该是将 i 后面得元素,依次覆盖前面得元素,达到删除的效果,所以我们是从前面依次往前挪一个位置,如下图:
代码如下:
bool ListDelete(SqList *&L,int i,ElemType &e)
{
int j;
if(i<1 || i> L->length) //参数i错误是返回false
return false;
i--; //将顺序表的逻辑序号转化为物理序号
e=L->data[i];
for(j=i;jL->length-1;j++) //将data[i]之后的元素前移一个位置
L->data[j] = L->data[j+1];
L->length--; //顺序表的长度减1
return true; //成功删除返回true
}
平均时间复杂度为O(n)。
初始化线性表
之后的内容,我们就简单描述一下
该功能就是构造一个空的线性表L,只需分配线性表的存储空间并将length域设为0。代码如下:
void InitList(SqList *&L)
{
L=(SqList *)malloc(sizeof(SqList)); //分配放线性表的空间
L->length=0; //置空线性表的长度为0
}
时间复杂度为O(1)。
销毁线性表
该功能是释放线性表L占用的空间。代码如下:
void DestroyList(SqList *&L)
{
free(L); //释放L所指的顺序表空间
}
当不在不需要顺序表时,务必调用DestryList基本运算释放其存储空间;否则,尽管系统会自动释放顺序表指针变量L,但不会自动释放L指向的储存空间,如此可能会导致内存泄漏。
本算法的时间复杂度O(1)。
判断线性表是否为空表
代码,如下:
bool ListEmpty(SqList *L)
{
return(L->length==0);
}
时间复杂度为O(1)。
求线性表的长度
代码如下:
int ListLength(SqList *L)
{
return (L->length);
}
时间复杂度O(1)。
输出线性表
代码如下:
void DispList(SqList *L)
{
for(int i=0;i< L->length;i++) //扫描顺序表输出各元素值
printf("%d",L->data[i]);
printf("\n");
}
时间复杂度为O(n)。