第二章 线性表
主要内容:
1.线性表的类型定义
2.线性表的顺序表示和实现
3.线性表的链式表示和实现
学习提要:
1.了解线性表的逻辑结构和物理结构
2.掌握两种存储结构的描述方法以及在每种存储结构上的基本操作的实现
3.理解两种存储结构的特点及其使用场合
重难点内容:
顺序表、链表及其操作实现
线性结构是一个数据元素的有序(次序)集 。
线性结构的基本特征:
(1)存在唯一的一个被称作“第一个”的数据元素
(2)存在唯一的一个被称作“最后一个”的数据元素
(3)除第一个外,集合中的每个数据元素均只有 一个前驱
(4)除最后一个外,集合中的每个数据元素均只有一个后继
2.1 线性表的类型定义
线性表:n个数据元素组成的有限序列。表示为(a1,a2,…,ai,ai+1,…,an)
例:英文字母表(A,B,C,…..Z)是一个线性表
逻辑特征:
·1<i<n时
ai的直接前驱是ai-1,a1无直接前驱
ai的直接后继是ai+1,an无直接后继
·元素同构,且不能出现缺项
线性表的长度:表中元素的个数n(n>=0),n=0 空表。
位序:元素ai在表中的位置数i 。
抽象数据类型线性表的定义如下:
ADT List {
数据对象:
D={ ai | ai ∈ElemSet,i=1,2,...,n, n≥0 }
数据关系:
R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n }
基本操作:
InitList( &L )
操作结果:构造一个空的线性表L。
DestroyList( &L )
初始条件:线性表L已存在。
操作结果:销毁线性表L。
ListEmpty( L )
初始条件:线性表L已存在。
操作结果:若L为空表,则返回TRUE,否则FALSE。
ListLength( L )
初始条件:线性表L已存在。
操作结果:返回L中元素个数。
GetElem( L, i, &e )
初始条件:线性表L已存在,1≤i≤ListLength (L)
操作结果:用e返回L中第i个元素的值。
LocateElem( L, e, compare( ) )
初始条件:线性表L已存在,compare( )是元素判定函数。
操作结果:返回L中第i个与e满足关系compare( )的元素的位序。若这样的元素
不存在,则返回值为0。
PriorElem( L, cur_e, &pre_e )
初始条件:线性表L已存在。
操作结果:若cur_e是L的元素,但不是第一个,则用pre_e 返回它的前驱,否
则操作失败,pre_e无定义。
NextElem( L, cur_e, &next_e )
初始条件:线性表L已存在。
操作结果:若cur_e是L的元素,但不是最后一个,则用next_e返回它的后继,
否则操作失败,next_e无定义。
ListTraverse(L, visit( ))
初始条件:线性表L已存在。
操作结果:依次对L的每个元素调用函数visit( )。一旦visit( )失败,则操作失败。
ClearList( &L )
初始条件:线性表L已存在。
操作结果:将L重置为空表。
PutElem( L, i, &e )
初始条件:线性表L已存在,1≤i≤ ListLength (L)
操作结果:L中第i个元素赋值同e的值。
ListInsert( &L, i, e )
初始条件:线性表L已存在,1≤i≤ ListLength (L) +1
操作结果:在L的第i个元素之前插入新的元素e,L的长度增1。
ListDelete(&L, i, &e)
初始条件:线性表L已存在且非空,1≤i≤ ListLength (L)
操作结果:删除L的第i个元素,并用e 返 回其值,L的长度减1。
} ADT List
利用上述定义的线性表可以实现其它更复杂的操作。
例2-1
假设:有两个集合A 和 B 分别用两个线性表 LA 和 LB 表示,即:线性表中的数据元素即为集合中的成员。
现要求一个新的集合A=A∪B。
算法思想:
要求对线性表作如下操作:扩大线性表 LA,将存在于线性表LB 中而不存在于线性表 LA 中的数据元素插入到线性表 LA 中去。
实现步骤:
1.从线性表LB中依次察看每个数据元素;
GetElem(LB, i ,e )
2.依值在线性表LA中进行查访;
LocateElem(LA, e, equal( ))
3. 若不存在,则插入之。
ListInsert(LA, n+1, e)
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); // 取Lb中第i个数据元素赋给e
if (!LocateElem(La, e, equal( )) )
ListInsert(La, ++La_len, e);
// La中不存在和 e 相同的数据元素,则插入之
} O(ListLength(LA)×ListLength(LB));
} // union
例2-2
已知线性表LA和LB是非递减的,将两表合并成新的线性表LC,且LC也是非递减的。
算法思想:
将LA、LB两表中的元素逐一按序加入到一个新表LC中。 设 La = (a1, …, ai, …, an),
Lb = (b1, …, bj, …, bm) ,Lc = (c1, …, ck, …, cm+n)
实现步骤:
1.分别从La和Lb中取得当前元素ai和bj;
2.若ai≤bj,则将ai插入到Lc中,否则将bj插入到Lc中。
void MergeList(List La, List Lb, List &Lc) {
// 本算法将非递减的有序表 La 和 Lb 归并为 Lc
InitList(Lc); // 构造空的线性表 Lc
i = j = 1; k = 0;
La_len = ListLength(La);
Lb_len = ListLength(Lb);
while ((i <= La_len) && (j <= Lb_len))
{ // La 和 Lb 均不空 }
while (i<=La_len) // 若 La 不空
while (j<=Lb_len) // 若 Lb 不空
} // merge_list O(ListLength(LA)+ListLength(LB))
while (i<=La_len)&&(j<=Lb_len)
{ //La和Lb均非空
GetElem(La,i,ai);
GetElem(Lb,j,bj);
if(ai<=bj)
{ ListInsert( Lc, ++k, ai); ++i;}
else
{ListInsert( Lc, ++k, bj); ++j;}
}
while (i <= La_len) { // 当La不空时
GetElem(La, i++, ai);
ListInsert(Lc, ++k, ai);
} // 插入 La 表中剩余元素
while (j <= Lb_len) { // 当Lb不空时
GetElem(Lb, j++, bj);
ListInsert(Lc, ++k, bj);
} // 插入 Lb 表中剩余元素
2.2 线性表的顺序表示和实现
线性表的顺序表示:用一组地址连续的储存单元依次储存线性表的数据元素。
a1(线性表的起始地址称作线性表的基地址) a2 … ai-1 ai … an
顺序表:
定义:顺序储存结构表示的线性表。
元素地址计算方法:
LOC(ai+1) = LOC(ai) + L
LOC(ai) = LOC(a1) + (i-1)*L
其中:
L—一个元素占用的存储单元个数
LOC(ai)—线性表第i个元素的地址
特点:
·实现逻辑上相邻—物理地址相邻
·实现随机存取
实现:可用C语言的一维数组实现
顺序表类型定义:
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
typedef struct {
ElemType *elem; // 基址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以 sizeof(ElemType)为单位
} SqList;
线性表的基本操作在顺序表中的实现:
InitList(&L) // 结构初始化
LocateElem(L, e, compare()) // 查找
ListInsert(&L, i, e) // 插入元素
ListDelete(&L, i,&e) // 删除元素
Status InitList_Sq( SqList& L ) {
// 构造一个空的线性表
L.elem = (ElemType*) malloc (LIST_
INIT_SIZE*sizeof (ElemType));
if (!L.elem) exit (OVERFLOW);
L.length = 0;
L.listsize = LIST_INIT_SIZE
return OK; 算法时间复杂度:O(1)
} // InitList_Sq
查找操作LocateElem(L, e, compare())
int LocateElem_Sq(SqList L, ElemType e,
Status (*compare)(ElemType, ElemType)) {
// 在顺序表中查询第一个满足判定条件的数据元素,
// 若存在,则返回它的位序,否则返回 0
i = 1; // i 的初值为第 1 元素的位序
p = L.elem; // p 的初值为第 1 元素的存储位置
while (i <= L.length &&
!(*compare)(*p++, e)) ++i;
if (i <= L.length) return i;
else return 0;
} // LocateElem_Sq 算法的时间复杂度为:O( ListLength(L) )
插入操作ListInsert_Sq(&L, i, e)
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
// 在顺序表L的第 i 个元素之前插入新的元素e,
// i 的合法范围为 1≤i≤L.length+1
……
q = &(L.elem[i-1]); // q 指示插入位置
for (p = &(L.elem[L.length-1]); p >= q; --p)
*(p+1) = *p;
*q = e; // 插入e
++L.length; // 表长增1
return OK;
} // ListInsert_Sq 算法时间复杂度为:O( ListLength(L) )
if (i < 1 || i > L.length+1) return ERROR;
// 插入位置不合法
if (L.length >= L.listsize) {
// 当前存储空间已满,增加分配
newbase = (ElemType *) realloc (L.elem,
(L.listsize+LISTINCREMENT)*sizeof (ElemType));
if (!newbase) exit(OVERFLOW); // 存储分配失败
L.elem = newbase; // 新基址
L.listsize += LISTINCREMENT; // 增加存储容量
}
算法时间复杂度T(n)
设Pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个元素时,所需移动的元素次数的平均次数为:
删除操作ListDelete(&L, i, &e)
Status ListDelete_Sq
(SqList &L, int i, ElemType &e) {
if ((i < 1) || (i > L.length)) return ERROR;
//删除位置不合法
p = &(L.elem[i-1]); // p 为被删除元素的位置
e = *p; //被删除元素的值赋给 e
q = L.elem+L.length-1; //表尾元素的位置
for (++p; p <= q; ++p) *(p-1) = *p;
// 被删除元素之后的元素左移
--L.length; //表长减1
return OK;
} // ListDelete_Sq 算法时间复杂度为:O( ListLength(L))
算法评价
设Qi是删除第i个元素的概率,则在长度为n的线性表中删除一个元素所需移动的元素次数的平均次数为:
顺序存储结构的优缺点
优点
逻辑相邻,物理相邻
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充