2.2.1 顺序表的定义
线性表的顺序存储又称顺序表。
它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻、第1个元素存储在线性表的起始位置,第i个元素存储位置后面紧接着的是第i+1个元素,称i为元素ai在线性表中的位序。
顺序表的特点是:
表中元素的逻辑顺序与其物理顺序相同。
【注】:物理结构(存储结构)是数据结构在计算机中的表示。
每个数据元素的存储位置都和线性表的起始位置相差一个和该数据元素的位序成正比的常数,因此,线性表中的任一数据元素都可以随机存取
线性表的顺序存储结构是一种随机存取的存储结构,通常用高级程序设计语言中的数组来描述线性表的顺序存储结构。
【注】线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的。
假定线性表的元素类型为ElemType,则线性表的顺序存储类型描述为:
#define Maxsize 50 // 定义线性表的最大长度
typedef struct{
ElemType data[MaxSize]; // 顺序表的元素
int length; // 顺序表的当前长度
}SqList; // 顺序表的类型定义
一维数组可以是静态分配的,也可以是动态分配的。
静态分配时:
由于数组的大小和空间事先已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。
动态分配时:
存储数组的空间是在程序执行过程中通过动态才能出分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数组空间的目的,而不需要为线性表一次性地划分所有空间。
#define InitSize 100 // 表长度的初始定义
typedef struct{
ElemType *data; // 指示动态分配数组的指针
int MaxSize,length; // 数组的最大容量和当前个数
} SeqList; // 动态分配数组顺序表的类型定义
C的初始动态分配语句为
L.data = (ElemType *)malloc(sizeof(ElemType) *Initsize);
【注】:动态分配并不是链式存储,它同样属于顺序存储结构,物理结构没有变化,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定。
顺序表
- 主要特点:随机存储(通过首地址和元素序号可以在时间O(1)内找到指定的元素)
- 存储密度高,每个结点只存储数据元素
- 逻辑上相邻的元素物理上也相邻,插入和删除操作需要移动大量的元素
2.2.2 顺序表上基本操作的实现
1)插入操作
在顺序表L的第i(1<=i<=L.length+1)个位置插入新元素e。若 i 的输入不合法,则返回 false,表示插入失败;否则,将第 i 个元素及其后的所有元素依次往后移动一个位置,腾出一个空位置插入新元素e,顺序表长度增加1,插入成功,返回true
bool 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;
}
};
【注意】:区别顺序表的位序和数组下标。
时间复杂度:
最好情况: | 在表尾插入(即i = n+1),元素后移语句将不执行,时间复杂度为O(1) |
---|---|
最坏情况: | 在表头插入(即i = 1),元素后移语句执行n次,时间复杂度为O(n) |
平均情况 | 假设Pi(Pi = 1/(n+1))是在第i个位置上插入一个结点的概率,则在长度为n的线性表中插入一个结点时,所需移动的平均次数为 n/2,线性表插入算法的平均时间复杂度为O(n) |
2)删除操作
删除顺序表L中第 i (1 <= i <= L.length)个位置的元素,用引用变量 e 返回。若 i 的输入不合法,则返回 false;否则,将被删除元素赋给引用变量 e,并将第 i+1 个元素及其后的所有元素依次往前移动一个位置,返回 true。
bool ListDelete(SqList &L, int i, Elemtype &e)
{
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;
}
最好情况: | 删除表尾元素(即i = n),无需移动元素,时间复杂度为O(1) |
---|---|
最坏情况: | 删除表头元素(即i = 1),需移动除表头元素外的所有元素,时间复杂度为O(n) |
平均情况: | 假设Pi(Pi = 1/n)是在第i个位置上插入一个结点的概率,则在长度为n的线性表中插入一个结点时,所需移动的平均次数为 (n-1)/2,线性表删除算法的平均时间复杂度为 O(n) |
顺序表中插入和删除操作的时间主要耗费在移动元素上,而移动的元素个数取决于插入和删除元素的位置。
3)按值查找(顺序查找)
在顺序表L中查找第一个等于 e 的元素,并返回其位序。
int LocateElem(SqList L,ElemType e)
{
int i;
for(i=0; i<L.length; i++)
{
if(L.data[i] == e)
{
return i+1; // 下标为 i 的元素值等于 e ,返回其位序 i+1
}
}
return 0; // 退出循环,说明查找失败
}
最好情况: | 查找的元素就在表头,时间复杂度为O(1) |
---|---|
最坏情况: | 查找的元素在表尾(或不存在),需要比较n次,时间复杂度为O(n) |
平均情况: | 假设Pi(Pi = 1/n)是查找元素在第i(1<=i<=L.length)个位置上的概率,则在长度为n的线性表中查找值为 e 的元素所需比较的平均次数为:(n+1)/2,时间复杂度为O(n) |