目录
线性表的操作如何实现?取决于线性表如何存储。第1节介绍了几种存储结构。
本节考虑线性表采用顺序存储,线性表的顺序表示又称为顺序存储结构或顺序映像。
一. 线性表的顺序表示
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
即:逻辑上相邻,物理上也相邻。
线性表的第1个数据元素的存储地址,称作线性表的起始位置或基地址。
例如:线性表(1,2,3,4,5,6)的存储结构:
下面第2个就不是一个顺序存储结构。线形表顺序存储结构占用一片连续的存储空间。知道某个元素的存储位置就可以计算其他元素的存储位置。假设线性表的每个元素需占L个存储单元,则第i+1个数据元素的存储位置和第i个数据元素的存储位置之间满足关系:
由此,所有数据元素的存储位置均可由第一个数据元素的存储位置得到:
计算存储位置这一运算的时间复杂度是O(1),与线性表规模n无关。这种直接获得地址的运算叫作随机存取。
存储结构反映了线性表在机器内存中的物理状态,数据结构在高级语言层次讨论数据结构的实现方法,需用高级语言已经实现的数据类型描述存储结构。
二. 顺序表的类型定义
顺序表的特征可类似于一维数组,考虑用数组表示线性表;然而,线性表的长度是可变的,C语言中一维数组的长度不可动态定义。考虑用一变量表示顺序表的长度属性。
typedef struct
{
ElemType *elem; // 存储空间的基地址
int length; // 当前长度
}SqList; // 顺序表的结构类型为 SqList
SqList是定义的结构数据类型,数组名是elem,前面的Elemtype是数组的类型。存储线性表分为两部分内容:一部分是数组,另一部分是一个整型变量,用来存储线性表中元素的个数。
例如:第4节我们讲过多项式的存储,只存储非零项的系数和指数:
对于多项式,线性表为
此线性表P可由下表示:
# define MAXSIZE 1000 //多项式可能达到的最大长度
typedef struct{ //多项式非零项元素的定义
float p; //系数
int e; //指数
}Polynomial;
typedef struct{
Polynomial *elem; //存储空间的基地址
int length; //多项式中当前项的个数
}SqList; //多项式的顺序存储结构类型为SqList
Polynomial定义每一个数据元素都由系数和指数两部分构成,然后我们在此基础上定义数组,实际长度用length表示。在C语言中,Polynomial *elem表示一个指向Polynomial类型的指针变量,指针变量名是elem,作用是存储一个首地址也就是每一系列的地址。Polynomial是一个结构类型,通过使用指针,我们可以在程序中动态地分配内存来存储Polynomial类型的对象,并通过指针来访问和操作这些对象。
例2:图书管理系统
# define MAXSIZE 1000 //图书表可能达到的最大长度
typedef struct{ //每一条图书信息的定义
char no[20] //图书ISBN信息
char name[50]; //图书名字
float price; //图书价格
}Book;
typedef struct{
Book *elem; //存储空间的基地址
int length; //图书表中当前项的个数
}SqList; //图书表的顺序存储结构类型为SqList
顺序表的示意图:
操作数组:L.elem,若定义L是指针(SqList *L),可以用L->elem
操作长度:L.length,若定义L是指针(SqList *L),可以用L->length
三. 顺序表基本操作的实现
首先说明操作算法中常用的预定义常量和类型:
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函数的类型,其值是函数结果状态代码,可根据实际问题需要修改
typedef int Status;
typedef char ElemType;
(1)线性表的初始化
Status InitList_Sq(SqList &L) //参数用引用型,见第6节详细说明
{
L.elem = new ElemType[MAXSIZE]; //动态分配空间,C++中的new语法用于动态分配内存并返回指向新分配内存的指针。
if(!L.elem) exit(OVERFLOW); //提高健壮性,等价于if(L.elem==NULL)
L.length = 0; //此时线性表中没有元素
return OK;
}
(2)销毁线性表
void DestroyList(SqList &L) //销毁线性表L
{
if (L.elem) delete L.elem; //释放存储空间
}
(3)清空线性表
void ClearList(SqList &L) //清空线性表L
{
L.length = 0; //将线性表的长度置零
}
(4)求线性表的长度
int GetLength(SqList L) //获取表长
{
return (L.length); //返回线性表的长度
}
(5)判断线性表是否为空
int IsEmpty(SqList L) //判断线性表是否为空
{
if (L.length == 0) return 1;
else return 0;
}
(6)顺序表取值
这里语句都只执行一次,复杂度为O(1),与顺序表长度无关
int GetElem(SqList L,int i,ElemType &e) //顺序表取第i个元素的内容
{
if (i<1||i>L.length) return ERROR; //判断i值是否合理,i必须取[1,L.length]之间的整数
e = L.elem[i-1]; //注意逻辑下标和物理下标差1,第1个元素存在elem[0],以此类推
return OK;
}
(7)顺序表的查找操作
在线性表L中查找与指定位置e相同的数据元素的位置。这里采用顺序查找法,从表的一端开始,逐个进行记录的关键字和给定值的比较。返回该元素的位置序号,未找到,返回0。
例如:在图书表中,按照给定书号进行查找,确定是否存在该图书
如果存在:输出是第几个元素;如果不存在:输出0
int LocateElem(SqList L,ElemType e) //顺序表L查找是否含有指定元素e
{
for (i=0;i<L.length;i++) //for循环从头开始查找,i最大可到n-1
if(L.elem[i] == e) return i+1; //如果有,返回下标,注意逻辑下标和物理下标差1
return 0; //查找失败,返回0
}
或者采用while循环:循环条件为i小于L.length(顺序表的长度)且L.elem[i]不等于e。循环体内的i++表示每次循环后i的值增加1,即继续查找下一个元素。
查找成功:如果循环结束后i仍然小于L.length,说明找到了指定元素e。此时返回i+1,表示找到的元素在顺序表中的位置。注意,返回的是逻辑下标(从1开始),而不是物理下标(从0开始),所以要加1。
查找失败:如果循环结束后i大于等于L.length,说明未找到指定元素e。此时返回0,表示查找失败。
int LocateElem(SqList L,ElemType e) //顺序表L查找是否含有指定元素e
{
i = 0;
while(i<L.length && L.elem[i]!=e) i++; //while循环从头开始查找
if(i<L.length) return i+1; //如果有,返回下标,注意逻辑下标和物理下标差1
return 0; //查找失败,返回0
}
顺序查找法算法分析:
平均查找长度ASL (Average Search Length):为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值叫做查找算法的平均查找长度。例如,对于一个L.length=7的线性表,它的ASL是:
对含有n个记录的表,查找成功时:
式中Pi是第i个记录被查找的概率,Ci是第i个记录需要被查找的次数;
在顺序查找中,假设每个记录查找的概率相等,则:
(8)顺序表的插入操作
定义:见第4节。
算法思想:(1)判断插入位置是否合法(插入位置从1号元素到L.length+1号元素);(2)判断顺序表存储空间是否已满,若已满返回ERROR;(3)将第n至第i位的元素依次向后移动一个位置,空出第i个位置;(4)将要插入的新元素e放入第i个位置;(5)表长加1,插入成功返回OK;
Status ListInsert_Sq(SqList &L,int i,ElemType e) //顺序表L在指定位置插入元素e
{
if(i<1||i>L.length+1) return ERROR; //存储位置不合法
if(L.length == MAXSIZE) return ERROR; //如果存储空间已满则不能插入
for (j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j]; //把插入位后面的元素全部后移1个位置
L.elem[i-1] = e; //在第i个位置插入元素e
L.length++; //表中元素个数+1
return OK; //插入成功
}
算法分析:算法时间主要耗费在移动元素的操作上。仿照前面的分析可得:
因此顺序表插入算法的平均时间复杂度为O(n)
(9)顺序表的删除操作
算法思想:(1)判断删除位置i是否合法(合法值为1≤i≤n) ;(2)将欲删除的元素保留在e中;(3)将第i+1至第n位的元素依次向前移动一个位置;(4)表长减1,删除成功返回OK;
Status ListDelete_Sq(SqList &L,int i) //顺序表L在指定位置删除元素
{
if(i<1||i>L.length) return ERROR; //删除位置不合法
for (j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j]; //把删除位后面的元素全部前移1个位置
L.length--; //表中元素个数-1
return OK; //删除成功
}
时间复杂度分析:
因此顺序表删除算法的平均时间复杂度为O(n)。
四. 小结
(1)利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致;
(2)在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等。这种存取元素的方法被称为随机存取法。
(3)顺序表的优缺点:
优点:存储密度大(结点本身所占存储量/结点结构所占存储量);可以随机存取表中任一元素;
缺点:在插入、删除某一元素时,需要移动大量元素;浪费存储空间;属于静态存储形式,数据元素的个数不能自由扩充;
为克服这一缺点->链表。