1.21k likes | 1.37k Views
第二章 线性表. 线性结构的基本特征 :. 线性结构 是 一个数据元素的有序(次序)集. 1 .集合中必存在唯一的一个“第一元素”. 2 .集合中必存在唯一的一个 “最后元素”. 3 .除最后元素在外,均有 唯一的后继. 4 .除第一元素之外,均有 唯一的前驱. 2.1 线性表的类型定义. 2.2 线性表类型的实现 顺序映象. 2.3 线性表类型的实现 链式映象. 2.4 一元多项式的表示. ADT List {. 数据对象 :.
E N D
第二章 线性表
线性结构的基本特征: 线性结构 是 一个数据元素的有序(次序)集 1.集合中必存在唯一的一个“第一元素” 2.集合中必存在唯一的一个 “最后元素” 3.除最后元素在外,均有 唯一的后继 4.除第一元素之外,均有 唯一的前驱
2.1 线性表的类型定义 2.2 线性表类型的实现 顺序映象 2.3 线性表类型的实现 链式映象 2.4 一元多项式的表示
ADT List { 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } { 称n为线性表的表长; 称n=0时的线性表为空表。} 数据关系: R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } { 设线性表为 (a1,a2, . . . ,ai,. . . ,an), 称 i 为 ai 在线性表中的位序。}
基本操作: 结构初始化操作 结构销毁操作 引用型操作 加工型操作 } ADT List
{结构初始化} InitList( &L ) 操作结果:构造一个空的线性表 L。 CreateList( &L, A[], n) 初始条件:A[]中存在线性表的 n 个 数据元素。 操作结果:构造一个含 n 个数据元素 的线性表 L。
{销毁结构} DestroyList( &L ) 初始条件:线性表 L 已存在。 操作结果:销毁线性表 L。
{引用型操作} ListEmpty( L ) ListLength( L ) PriorElem( L, cur_e, &pre_e ) NextElem( L, cur_e, &next_e ) GetElem( L, i, &e ) LocateElem( L, e, compare( ) ) ListTraverse(L, visit( ))
ListEmpty( L ) (线性表判空) 初始条件: 操作结果: 线性表 L 已存在。 若 L 为空表,则返回 TRUE,否则FALSE。 ListLength( L ) (求线性表的长度) 初始条件: 操作结果: 线性表 L 已存在。 返回线性表 L中元素个数。
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无定义。
GetElem( L, i, &e )( 求线性表中某个元素) 初始条件: 操作结果: 且1≤i≤LengthList(L)。 线性表 L 已存在 以 e带回 L中第 i个数据元素值。 LocateElem( L, e, compare( ) )(定位函数) 初始条件: 操作结果: 线性表 L 已存在,compare()为判定函数。 返回 L 中第 1 个与 e 满足关系compare( )的元素的位序,若不存在这样的元素,则返回0。
ListTraverse( L, visit( ) )( 遍历线性表) 初始条件: 操作结果: 线性表 L 已存在,visit( )为数据元素的访问函数。 依次对 L的每个数据元素调用函数 visit( ),一旦 visit( ) 失败,则遍历操作失败。
{加工型操作} ClearList( &L ) ( 线性表置空 ) PutElem( &L, i, &e ) ( 改变数据元素的值 ) ListInsert( &L, i, e ) ( 插入数据元素 ) ListDelete( &L, i, &e ) ( 删除数据元素 )
ClearList( &L ) 初始条件: 操作结果: 线性表 L 已存在。 将 L 重置为空表。 PutElem( &L, i, e ) 初始条件: 操作结果: 线性表 L 已存在, 且 1≤i≤LengthList(L)。 L 中第 i 个元素赋值和 e 相同。
ListInsert( &L, i, e ) ,1≤i≤LengthList(L)+1。 初始条件: 操作结果: 线性表 L 已存在 在 L 的第 i 个元素之前插入新的元素e, L 的长度增 1。 ListDelete( &L, i, e ) 初始条件: 操作结果: ,1≤i≤LengthList(L)。 线性表 L 已存在 删除 L 中第 i 个元素,并以 e 带回其值, L 的长度减 1。
例 2-1 假设有两个集合 A 和 B 分别用两个线性表 LA 和 LB 表示,即:线性表中的数据元素即为集合中的成员。 现要求一个新的集合A=A∪B。 即:需对线性表作如下操作: 扩大线性表 LA,将存在于线性表LB 中而不存在于线性表 LA 中的数据元素插入到线性表 LA 中去。
操作步骤: 1.取得线性表 LB 中一个数据元素; ListDelete(Lb, 1, e); 2.依值在线性表 LA 中进行查访; LocateElem(LA, e, equal( )); 3.若不存在,则插入之。 ListInsert(LA, n+1, e); ( n 表示线性表 LA 当前长度)
void union(List &La, List Lb) { // 将线性表Lb中所有在线性表La中不存在的数据元素 // 插入到线性表La中 La_len = ListLength(La);// 求线性表La的长度 while (!ListEmpty(Lb)) { ListDelete(Lb, 1, e);// 删除Lb中第1个数据元素赋给e if (!LocateElem(La, e, equal( )) ) ListInsert(La, ++La_len, e); // La中不存在和 e 相同的数据元素,则插入之 }//while } // union
例 2-2 已知一个“非纯集合” B,试构造一个纯集合A,使 A 中只包含 B 中所有值各不相同的数据元素。 若仍然以线性表表示集合,则算法的策略应该和例一基本相同,差别是什么? 1.集合 A 的初态是“空集” 2.不破坏集合 B
void purge(List &La, List Lb) { La_len=0; Lb_len=ListLength(Lb); } // pugre InitList(La); // 构造(空的)线性表LA for (i = 1; i <= Lb_len; i++) { }//for GetElem(Lb, i, e); // 取Lb中第 i 个数据元素赋给 e if (!LocateElem(La, e, equal( )) ) ListInsert(La, ++La_len, e); // La中不存在和 e 相同的数据元素,则插入之
判别两个集合是否相等。 例 2-3 集合相等的条件是:两者所含元素个数相同,且所有对应元素都相等。 仍以线性表表示集合,并假设这两个集合是“纯集合”。 则算法的策略为:在两个线性表的长度相等的前提下,只要判别一个表中的元素在另一个表中都存在即可。
bool isEqual(List LA, List LB) { // 若线性表LA和LB不仅长度相等,且所含数据 // 元素也相同,则返回 TRUE, 否则返回 FALSE }//isEqual La_len = Listlength(LA); Lb_len = Listlength(LB); if ( La_len != Lb_len ) return FALSE; // 两表的长度不等 else {} ………
while ( i<= La_len && found ) { }//while return found; i = 1; found = TRUE; GetElem(LA, i, e); // 取得LA中一个元素 if (LocateElem(LB, e, equal( )) i++; // 依次处理下一个 else found = FALSE; // LB中没有和该元素相同的元素
一、顺序表 ---线性表的顺序映象 二、顺序表中基本操作的实现 三、顺序表的其它操作举例
顺序映象 ——以 x 的存储位置和 y 的存储位置之间某种关系表示逻辑关系<x,y> 最简单的一种顺序映象方法是: 令 y 的存储位置和 x 的存储位置相邻。
a1 a2… ai-1 ai… an 用一组地址连续的存储单元 依次存放线性表中的数据元素 线性表的起始地址, 称作线性表的基地址
以“存储位置相邻”表示有序对<ai-1,ai> 即:LOC(ai) = LOC(ai-1) + C 一个数据元素所占存储量↑ 所有数据元素的存储位置均取决于 第一个数据元素的存储位置 LOC(ai) =LOC(a1)+ (i-1)×C 基地址
顺序表的 C 语言描述 // 存储结构 typedef struct { } SqList; // 俗称 顺序表 ElemType *elem; // 存储空间基址 int length; // 当前长度 int listsize; // 当前分配的存储容量 // (以sizeof(ElemType)为单位) // 基本操作接口
void purge(SqList &La, SqList Lb) { // 构造顺序表La,使其只包含Lb中所有值不 //相同的数据元素, 算法不改变顺序表Lb bool b; int Lb_len = Listlength(Lb); // 求线性表Lb的长度 InitList(La, Lb_len); // 创建一个空的线性表La int La_len = 0; for (i = 1; i <= Lb_len; i++){ // 依次处理Lb中每个元素 }//for } //purge
b = GetElem(Lb, i, e); // 取Lb中第i个数据元素赋给e if (!LocateElem(La, e, equal( )) { ++La_len; b = ListInsert(La, La_len, e); //当La中不存在和e 值相同的数据 //元素时进行插入 }
线性表的基本操作在顺序表中的实现 InitList(&L) // 结构初始化 LocateElem(L, e, compare()) // 查找 ListInsert(&L, i, e) // 插入元素 ListDelete(&L, i) // 删除元素
Status InitList( SqList& L, int maxsize ) { // 构造一个最大容量为 maxsize 的顺序表 } // InitList L.elem = new ElemType[maxsize]; //为顺序表分配大小为 maxsize 的数组空间 if (!L.elem) exit(OVERFLOW); L.length = 0; L.listsize = maxsize; return OK; O(1) 算法时间复杂度:
L.listsize L.elem 23 75 41 38 54 62 17 p p p p p p L.length = 7 例如:顺序表 可见,基本操作是, 将顺序表中的元素 逐个和给定值 e 相比较。 i = 1 2 3 4 1 8 假设 e = 50 38
int LocateElem(SqList L, ElemType e, Status (*compare)(ElemType, ElemType)) { //在顺序表中查询第一个满足判定条件的数据元素, // 若存在,则返回它的位序,否则返回 0 } // LocateElem i = 1; // i 的初值为第 1 元素的位序 p = L.elem;// p 的初值为第 1 元素的存储位置 while (i <= L.length && !(*compare)(*p++, e)) ++i; (*compare)(*p++, e) if (i <= L.length) return i; else return 0; //找到满足条件的元素 // 没有找到满足条件的元素 算法的时间复杂度为: O( ListLength(L) )
线性表操作 ListInsert(&L, i, e)的实现: 首先分析: 插入元素时, 线性表的逻辑结构发生什么变化 ?
a1 a2… ai-1 ai… an a1 a2… ai-1 ai … an e 表的长度增加 (a1, …, ai-1, ai, …, an) 改变为 (a1, …, ai-1, e, ai, …, an) <ai-1, ai> <ai-1, e>, <e, ai>
Status ListInsert(SqList &L, int i, ElemType e) { //在顺序表L的第 i 个元素之前插入新的元素e, // i 的合法范围为 1≤i≤L.length+1 } // ListInsert …… for (j=L.length-1; j>=pos-1; --j) L.elem[j+1] = L.elem[j]; // 插入位置及之后的元素右移 L.elem[pos-1] = e; // 插入e ++L.length; // 表长增1 return TRUE; 元素右移 算法时间复杂度为: O( ListLength(L) )
考虑移动元素的平均情况: 假设在第i 个元素之前插入的概率为 pi, 则在长度为n的线性表中插入一个元素所需移动元素次数的期望值为: 若假定在线性表中任何一个位置上进行插入的概率都是相等的,则移动元素的期望值为:
if (i < 1 || i > L.length+1) return ERROR; //插入位置不合法 if (L.length >= L.listsize) return OVERFLOW; // 当前存储空间已满
j j j 21 18 30 75 42 56 87 21 18 30 75 例如:ListInsert(L, 5, 66) for (j=L.length-1; j >=pos-1; --j) L.elem[j+1]= L.elem[j]; 0 4 L.length-1 66 42 56 87
线性表操作 ListDelete(&L, i, &e)的实现: 首先分析: 删除元素时, 线性表的逻辑结构发生什么变化?
a1 a2… ai-1 aiai+1 … an a1 a2… ai-1 (a1, …, ai-1, ai, ai+1, …, an) 改变为 (a1, …, ai-1, ai+1, …, an) <ai-1, ai>, <ai, ai+1> <ai-1, ai+1> ai+1 … an 表的长度减少
Status ListDelete (SqList &L, int i, ElemType &e) { } // ListDelete if ((i < 1) || (i > L.length)) return ERROR; // 删除位置不合法 for (j = pos; j<L.length; ++j) L.elem[j-1] = L.elem[j]; // 被删除元素之后的元素左移 --L.length; // 表长减1 returnTRUE; 元素左移 算法时间复杂度为: O( ListLength(L))
考虑移动元素的平均情况: 假设删除第i 个元素的概率为 qi , 则在长度为n 的线性表中删除一个元素所需移动元素次数的期望值为: 若假定在线性表中任何一个位置上进行删除的概率都是相等的,则移动元素的期望值为:
j j j 21 18 30 75 42 56 87 63 21 18 30 75 例如:ListDelete(L, 5, e) for (j=pos; j<L.length; ++j) L.elem[j-1] = L.elem[j]; 0 5 L.length-1 56 87 63
例 2-4 试设计一个算法,用尽可能少的辅助空间将顺序表中前 m 个元素和后 n 个元素进行互换,即将线性表 (a1, a2, …, am, b1, b2, …, bn ) 改变成 (b1, b2, …, bn, a1, a2, …, am ) 。 算法 1 从b1开始,从原地删除之后插入到 a1 之前直至 bn。 算法 2 进行三次“逆转顺序表”的操作。
i i i j j j g 1 2 a 3 a b c b a d b c c e d d f e e f g f g 1 2 3 5 1 4 2 3 4 2 5 1 g a f b e c d e c f b a g
void invert( ElemType &R[],int s, int t ) // 本算法将数组 R 中下标自 s 到 t 的元素逆置, // 即将(Rs, Rs+1, …, Rt-1, Rt) // 改变为(Rt, Rt-1, …, Rs+1, Rs) void exchange ( SqList &A; int m ) { // 本算法实现顺序表中前 m 个元素 // 和后 n 个元素的互换 n = A.length – m; invert( A.elem, 0, A.length ); invert( A.elem, 0, n-1 ); invert( A.elem, n, m+n-1 ); } // exchange 算法的时间复杂度为: O(m+n)
void exchange( SqList &A, int m ) { // 实现顺序表 A 中前 m 个元素和后 n 个元素互换 } exchange for ( i=0; j = m; j<A.length; i++,j++ ) { } x = A.elem[j]; for ( k=j; k>i; k-- ) A.elem[j] = A.elem[j-1]; A.elem[i] = x; 算法的时间复杂度 为: O(mn)
例 2-5 试编写算法,删除顺序表中“多余” 的数据元素 。 从a1开始,检查在它之后的数据元素,如有和它相等的,则从表中删除之。 算法 1 回顾例2-2的算法,试按“构造”一个新的顺序表的思想来进行,设想这两个表“共享”一个存储空间。 算法 2