目录
一、顺序表的定义
线性表的顺序存储又称顺序表。顺序表的特点是表中元素的逻辑顺序与其存储的物理顺序相同。假设顺序表L存储的起始位置为LOC(A),sizeof(ElemType)是每个数据元素所占用存储空间的大小,则表L所对应的顺序存储如图所示。
每个数据元素的存储位置都和顺序表的起始位置相差一个和该数据元素的位序成正比的常数,因此,顺序表中的任意一个数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的存储结构。
顺序表的静态分配和动态分配
假设线性表的元素类型为ElemType(一种抽象数据类型,它可以是整型、字符型、浮点型或者用户自定义类型),则静态分配的顺序表的存储结构描述为
#define MaxSize 50 //定义线性表的最大长度
typedef struct {
ElemType data[MaxSize]; //使用静态"数组"存放数据元素
int length; //顺序表当前长度
}SqList; //顺序表类型定义
使用静态分配,数组大小事先已经固定,一旦空间占满,加入新数据会导致溢出,进而导致程序崩溃;如果设置较大的空间则会造成空间的浪费。而在动态分配时,一旦数据空间占满,就开辟一块更大的存储空间,将原表中的元素全部拷贝到新空间,从而达到扩充数组的存储空间的目的,而不需要为线性表一次性划分所有空间。
动态分配的顺序表的存储结构描述为
#define InitSize 100 //表长度的初始定义
typedef struct {
ElemType *data; //指示动态分配数组的指针,指向存储空间的起始地址
int MaxSize,length; //顺序表的最大容量和当前长度
}SeqList;
动态分配并不是链式存储,它同样属于顺序存储结构,物理结构没有变化,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定。
二、顺序表的基本操作
1. 顺序表的初始化
静态分配和动态分配的顺序表初始化操作是不同的。静态分配在声明一个顺序表时,就已为其分配了数组空间,因此初始化只需将顺序表的当前长度设为0。
void InitList(SqList &L){
L.length=0; //顺序表初始长度为0
//这一步不能省略,因为内存中会遗留"脏数据"
}
动态分配的初始化为顺序表分配一个预定义大小的数组空间,并将顺序表的当前长度设为0。MazSize指示顺序表当前分配的存储空间大小,一旦因插入元素而空间不足,就进行再分配。
C的初始动态分配语句为 L.data=(ElemType *)malloc(InitSize * sizeof(ElemType));
C++的初始动态分配语句为 L.data=new ElemType[InitSize];
//顺序表动态分配初始化
void InitList(SeqList &L){
//用malloc函数申请一片连续的内存空间
L.data=(ElemType *)malloc(InitSize * sizeof(ElemType)); //分配存储空间
//ElemType对应于定义的指针类型
L.length=0;
L.MaxSize=InitSize; //初始存储容量
}
当空间不足时,增加动态数组的长度
void IncreaseSize(SeqList &L, int len){
int *p=L.data; //指针p和data指向了同一位置
//用malloc函数申请一片连续的内存空间,不仅可以存放当前的所有数据元素,还可以多存len个数据元素
L.data=(ElemType *)malloc((L.MaxSize+len) * sizeof(ElemType));
for(int i=0;i<L.length;i++){
L.data[i]=p[i]; //将数据复制到新区域
}
L.MaxSize=L.MaxSize+len; //容量增加len
free(p); //释放原来的内存空间
}
2. 插入操作
以下代码建立在顺序表的“静态分配”实现方式之上,“动态分配”也雷同。
void ListInsert(SqList &L,int i,ElemType e){
if(i<1||i>L.length+1) //判断i的范围是否有效
return false;
if(L.length>=MaxSize) //当前空间已满,不能插入
return false;
for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
L.data[j]=L.data[j-1];
L.data[i-1]=e; //在位置i处放入e
L.length++; //线性表长度加1
return true;
}
插入操作的算法时间复杂度如下
3. 删除操作
bool ListDelete(SqList &L,int i,ElemType &e){
/*e加了引用符号&,那么它和main函数的e在内存中对应的是同一份数据,
否则main函数中e的值没有改变;同理,L前面也要加&符号*/
if(i<1||i>L.length) //判断i的范围是否有效
return false;
e=L.data[i-1]; //将被删除的元素赋给e
for(int j=i;j<L.length;j++) //将第i个位置后的元素前移
L.data[j-1]=L.data[j];
L.length--; //线性表长度减1
return true;
}
int main(){
SqList L;
InitList(L);
//...
int e=-1; //内存开辟空间,存放e,把删除的元素"带回来"
if(ListDelete(L,3,e))
printf("已删除第3个元素,其值为=%d\n",e);
else
printf("位序i不合法,删除失败\n");
return 0;
}
删除操作的算法时间复杂度如下
4. 查找操作
按位查找
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
//静态分配和动态分配都可以通过数组下标方式进行查找
ElemType GetElem(SqList L,int i){
//注意ElemType与定义的data数据类型一致
return L.data[i-1];
}
按位查找的时间复杂度为O(1),这也体现了顺序表的随机存取特性,可以根据起始地址和数据元素大小立即找到第i个元素。
按值查找
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
//在顺序表中查找第一个元素等于值等于e的元素,并返回其位序
int LocateElem(SqList L,ElemType e){
for(int i=0;i<L.length;i++)
if(L.data[i]==e)
return i+1; //下标为i的元素值等于e,返回其位序i+1
return 0; //退出循环,说明查找失败
}
注意:C语言中结构体的比较不能直接用 “==”
按值查找的时间复杂度为
三、顺序表的优缺点
顺序表的主要优点:①可进行随机访问,即可通过首地址和元素序号可以在O(1)时间内找到指定的元素;②存储密度高,每个结点只存储数据元素。顺序表的缺点也很明显:①元素的插入和删除需要移动大量的元素,插入操作平均需要移动n/2个元素,删除操作平均需要移动(n-1)/2个元素;②顺序存储分配需要一段连续的存储空间,不够灵活。