线性表
线性表的逻辑结构
线性结构的基本特征
线性结构是一个数据元素的有序(次序)集。
- 集合中必存在唯一的一个“第一元素”;
- 集合中必存在唯一的一个“最后元素”;
- 除最后元素外,均有唯一的后继;
- 除第一元素外,均有唯一的前驱。
线性表的定义
ADT List{
数据对象:
D={ai | ai | ElemSet, i=1,2,3,…,n, n 0 }
{称n为线性表的表长;称n=0时的线性表为空表}数据关系:
R1= { <ai-1 ,ai > | ai-1 ,ai D, i=2,…,n }
{ '<'和 '>'是有具体含义的,表示ai-1是ai的直接前驱,ai是ai-1的直接后继,设线性表为(a1, a2,…,ai,…, an),称i为ai在线性表中的位序。}基本操作:
- 结构初始化操作
- 结构销毁操作
- 引用型操作:使用这个数据结构
- 加工型操作:对这个数据结构的参数或长度等进行改动
其它的复杂操作可以靠基本操作组合实现
}ADT List
容易混的概念:引用符号&,和引用型操作没有关系
- 结构初始化操作
InitList(&L)
结果:构造一个空的线性表L - 结构销毁操作
DestroyList(&L)
初始条件:线性表L已存在
结果:销毁线性表L - 引用型操作
- 遍历线性表
ListTraverse(L,visit())
初始条件:线性表L已存在;visit()
是一个访问函数。
操作结果:依次对L中每个元素调用函数visit()
,一旦visit()
失败,则访问失败。 - 判断线性表是否为空
ListEmpty(L)
- 获得线性表长度
ListLength(L)
- 获得指定位序的元素的前驱
PriorElem(L,cur_e,&pre_e)
- 获得指定位序的元素的后继
NextElem(L,cur_e,&next_e)
- 获得指定位序的元素
GetElem(L,i,&e)
- 获得符合条件的指定元素在线性表中的位序
LocateElem(L,e,compare())
- 遍历线性表
- 加工型操作:
- 将线性表清空
ClearList(&L)
- 对指定位序的元素重新赋值
PutElem(&L,i,&e)
- 将元素插入指定位序之后
ElemInsert(&L,i,e)
- 删除指定位序的元素
DeleteElem(&L,i,&e)
- 将线性表清空
遍历:按照一定的规则对数据结构中每个元素访问且只访问一次。
- 注意规则
- 访问且只访问一次
- 访问是一个抽象的概念,对于不同的问题需要进行细化
利用上述定义的线性表类型实现其它的更复杂操作
例1.
假设:有两个集合A和B分别用两个线性表LA和LB表示,即:线性表中的数据元素即为集合中的成员。
现要求一个新的集合A=A
B。
上述问题可演绎为:
对线性表作如下操作:
扩大线性表LA,将存在于线性表LB中而不存在于线性表LA中的数据元素插入到线性表LA中去。
操作步骤:
- 从线性表LB中依次查看每个数据元素;
e
GetElem(LB,i,&e)
- 依值在线性表LA中进行查找;
LocateElem(LA,e,equal())
- 若不存在,则插入;
ElemInsert(&LA,n+1,e)
(n表示线性表LA当前长度,线性表位序从1开始)
void union(List& LA,List LB)
{
LA_len=ListLength(LA);
LB_len=ListLength(LB);
for(i=1;i<=LB_len;i++)
{
GetElem(LB,i,&e);
if(!LocateElem(LA,e,equal()))
ListInsert(&LA,++LA_len,e);
}//for
}//union
例2.
归并两个“其数据元素按值非递减有序排列”的有序表LA和LB,求得有序表LC也具有同样特性。
注意:归并和求并集的区别,归并是把两个集合所有元素合在一起,没有舍弃任何元素。
设La=(a1,…,ai,…,an),
Lb=(b1,…,bj,…,bm),
求得
Lc=(c1,…,ck,…,cm+n)
且已由(a1,…,ai-1)和(b1,…,bj-1)归并得(c1,…,ck-1)
则
基本操作:
- 初始化LC为空表;
- 分别从LA和LB中取得当前元素ai和bj;
- 若ai bj,则将ai插入到LC中,否则将bj插入到LC中;
- 重复2和3,直至LA或LB中元素被取完为止;
- 将LA表或LB表剩余元素按顺序插入到LC中。
void MergeList(List La,List Lb,List& Lc)
{
InitList(&Lc);
La_len=ListLength(La);
Lb_len=ListLength(Lb);
i=1,j=1,k=0;
while((i<=La_len)&&(j<=Lb_len))
{
GetElem(La,i,&a);
GetElem(Lb,j,&b);
if(a>=b)
{
ElemInsert(&Lc,++k,b);
j++;
}
else
{
ElemInsert(&Lc,++k,a);
i++;
}
}
while(i<=La_len)
{
GetElem(La,i++,&a);
ElemInsert(&Lc,++k,a);
}
while(j<=Lb_len)
{
GetElem(Lb,j++,&b);
ElemInsert(&Lc,++k,b);
}
}
线性表的顺序存储结构
1.顺序映像
以x的存储位置和y的存储位置之间某种关系表示逻辑关系<x,y>
最简单的一种顺序映像方法是:
令y的存储位置和x的存储位置相邻。
用一组地址连续的存储单元依次存放线性表中的数据元素。
a1 | a2 | … | ai-1 | ai | … | an |
---|
线性表的起始地址,称做线性表的基地址。
以“存储位置相邻”表示有序对<ai-1,ai>
即:LOC(ai)=LOC(ai-1)+C,C是一个元素所占存储量。
所有数据元素的存储位置均取决于第一个数据元素的存储位置。
LOC(ai)=LOC(a1)+(i-1)*C
LOC(a1)是基地址。
所以,顺序表的按序号查找是一种随机存取结构。
注意:
- 存取结构和存储结构是不同的概念。
- 存取结构是在一个数据结构上对查找操作的时间性能的一种描述。
- 通常有两种存取结构:随机存取结构和顺序存取结构。
- 随机存取结构是指在一个数据结构上进行查找的时间性能是O(1),即查找任意一个数据元素的时间是相等的,均为常数时间。
- 顺序存储结构是指在一个数据结构上进行查找的时间性能是O(n),即查找一个数据元素的时间复杂度是线性的,与该元素在结构中的位置有关。
顺序映像的c语言描述
线性表的静态分配顺序存储结构:
#define LISTSIZE 100 //存储空间最大分配量
typedef struct{
int elem[LISTSIZE];
int length;//当前长度
}Sqlist;
线性表的静态分配顺序存储结构中,线性表的最多数据元素为LISTSIZE
,元素数量不能随意增加,这是以数组方式描述线性表的缺点。
为了实现线性表最大存储数据元素可随意变化,可以使用一个动态分配的数组来取代上面的固定长度数组,如下描述。
线性表的动态分配顺序存储结构:
#define LIST_INIT_SIZE 100 //初始分配量
#define LISTINCREMENT 20 //分配增量
typedef struct{
int* elem; //存储空间基址
int length;//当前长度
int listsize;//当前分配的存储容量
//以sizeof(int)为单位
}Sqlist;
线性表的基本操作在顺序表中的实现
- 静态分配线性表中InitList(&L);的实现:
静态分配线性表的初始化在结构体中已经定义了,给length一个初值就可以了。
int InitList(Sqlist& L)
{
L.length=0;
return 0;
}
- 动态分配线性表中InitList(&L);的实现:
int InitList(Sqlist& L)
{
L.elem=(int *)malloc(LIST_INIT_SIZE*sizeof(int));
if(!L.elem) return -1;//存储分配失败
L.length=0;
L.listsize=LIST_INIT_SIZE;//初始存储容量
return 0;
}
- LocateElem(L,x,compare());
使用顺序查找
int LocateElem(Sqlist L,int val){
int n = L.length;
for (int i=0; i < n; i++){
if (L.elem[i] == val) return i;
}
return -1;
}
设元素都是等概率的,顺序表按值查找的平均比较次数是n/2,时间复杂度O(n)。
- 静态分配线性表的ListInsert(&L,i,e);实现
//在第i个元素之前插入指定元素
int ElemInsert(Sqlist& L, int i, int val)
{
int n = L.length;
if (n >= LISTSIZE) return -1; //溢出判断
if (i < 0 || i > n) return -1; //插入位置合法性判断
int* q = L.elem + i - 1; //q指向插入位置
int* p = L.elem + L.length - 1; //p指向线性表末尾元素
for (; p >= q; p--) {
*(p+1) = *p; //插入位置及之后的元素右移
}
*p = val; //插入val
++L.length; //线性表表长加1
return 0;
}
移动元素的平均情况:
假设在第
个元素之前插入的概率为
,则在长度为n的线性表中插入一个元素所需移动元素次数的期望值为:
若在线性表上任何一个位置进行插入的概率相等
,则移动元素的期望值为:
时间复杂度是
。
- DeleteElem(&L,i,&e);实现
//删除第i个元素
int ElemDelete(Sqlist& L, int i, int &val){
int n = L.length;
if (i < 0 || i >= n) return -1; //删除位置合法性判断
int* p = &(L.elem[i-1]); //p指向被删除位置
val = *p; //返回删除元素
int* q = L.elem+L.length - 1; //q指向线性表表尾元素
for (; p < q; p++) {
*p = *(p + 1); //被删除元素之后的元素左移
}
--L.length; //线性表长度减1
return 0;
}
移动元素的平均情况:
设删除第
个元素的概率为
,则在长度为
的线性表中删除一个元素所需移动元素次数的期望值为:
若在线性表上任何一个位置进行插入的概率相等
,则移动元素的期望值为:
算法时间复杂度显然也是O(n)。