活动地址:CSDN21天学习挑战赛
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…
…
反之,线性表的删除操作是使长度为 n 的线性表
( a 1 , ⋯ , a i − 1 , a i , a i + 1 , ⋯ , a n ) \qquad (a_1,\cdots,a_{i-1},a_i,a_{i + 1}, \cdots, a_n) (a1,⋯,ai−1,ai,ai+1,⋯,an)
变成长度为 n - 1 的线性表
( a 1 , ⋯ , a i − 1 , a i + 1 , ⋯ , a n ) \qquad (a_1,\cdots,a_{i-1},a_{i + 1},\cdots, a_n) (a1,⋯,ai−1,ai+1,⋯,an)
数据元素 a i − 1 a_{i-1} ai−1、 a i a_i ai 和 a i + 1 a_{i + 1} ai+1 之间的逻辑关系发生变化,为了在存储结构上反映这个变化,同样需要移动元素。
一般情况下,删除第
i
(
1
≤
i
≤
n
)
i(1 \leq i \leq n)
i(1≤i≤n) 个元素时需将从第 i + 1 至第 n(共 n - i)个元素依次向前移动一个位置,如算法 2.5 所示。
\;
//线性表的删除
Status ListDelete_Sq (Sqlist &L, int i, ElemType &e){
//在顺序线性表 L 中刪除第 i 个元素,并用 e 返回其值
// i 的合法值为 1 ≤ i ≤ ListLength_Sq(L)
if ((i < 1) || (i > L.length)) return ERROR; //i 值不合法
ElemType * p = & (L.elem[i - 1]); // p 为被删除元素的地址
e = *p; //被删除元素的值赋给 e
ElemType * q = L.elem + L.length - 1; //表尾元素的地址
for (++p; p <= q; ++p)
* (p - 1) = * p; //被删除元素之后的元素左移
-- L.length; //表长减 1
return OK;
}// ListDelete.Sq
从算法 2.4 和 2.5 可见,当在顺序存储结构的线性表中某个位置上插入或删除一个数据元素时,其时间主要耗费在移动元素上(换句话说,移动元素的操作为预估算法时间复杂度的基本操作),而移动元素的个数取决于插入或删除元素的位置。
假设 p 是在第 i 个元素之前插入一个元素的概率,则在长度为 n 的线性表中插入一个元素时所需移动元素次数的期望值(平均次数)为
E i s = ∑ i = 1 n + 1 p i ( n − i + 1 ) ( 2 − 3 ) \qquad E_{\mathrm{is}} = \sum_{i=1}^{n+1} p_{i}(n - i + 1) \qquad\qquad\qquad\qquad\quad (2-3) Eis=∑i=1n+1pi(n−i+1)(2−3)
数学期望(mean)(或均值,亦简称期望)是试验中 每次可能结果的
概率
乘以 其结果
的总和
假设 q 是删除第个元素的概率,则在长度为 n 的线性表中删除一个元素时所需移动元素次数的期望值(平均次数)为
E d l = ∑ i = 1 n q i ( n − i ) ( 2 − 4 ) \qquad E_{dl} = \sum_{i=1}^{n} q_{i}(n-i) \qquad\qquad\qquad\qquad\qquad\quad (2-4) Edl=∑i=1nqi(n−i)(2−4)
不失一般性,我们可以假定在线性表的任何位置上插入或删除元素都是等概率的,即
p
i
=
1
n
+
1
,
q
i
=
1
n
\qquad
则式(2-3) 和(2-4) 可分别简化为式(2-5) 和(2-6):
E
i
n
=
1
n
+
1
∑
i
=
1
n
+
1
(
n
−
i
+
1
)
=
n
2
(
2
−
5
)
\qquad
因为
$\qquad\sum_{i=1}^{n+1}(n-i+1) = (n - 1 + 1 ) + \cdots + (n - (n + 1) + 1) $= n + ⋯ + 0 \qquad\qquad\qquad\qquad\quad\;\; = n + \cdots + 0 =n+⋯+0
= ( n + 1 ) ( 0 + n ) 2 \qquad\qquad\qquad\qquad\quad\;\; = \Large\frac{(n + 1)(0 + n)}{2} =2(n+1)(0+n)
= n ( n + 1 ) 2 \qquad\qquad\qquad\qquad\quad\;\; = \Large\frac{n (n + 1)}{2} =2n(n+1)
所以
E i n = 1 n + 1 ∑ i = 1 n + 1 ( n − i + 1 ) \qquadEin=n+11i=1∑n+1(n−i+1)" role="presentation" style="position: relative;"> E i n = 1 n + 1 ∑ i = 1 n + 1 ( n − i + 1 ) = 1 n + 1 ⋅ n ( n + 1 ) 2
=n+11⋅2n(n+1)" role="presentation" style="position: relative;"> = 1 n + 1 ⋅ n ( n + 1 ) 2 = n 2
=2n" role="presentation" style="position: relative;"> = n 2
E
d
l
=
1
n
∑
i
=
1
n
(
n
−
i
)
=
n
−
1
2
(
2
−
6
)
\qquad
同理可得上述结论
由式
(
2
−
5
)
(2-5)
(2−5) 和
(
2
−
6
)
(2-6)
(2−6) 可见,在顺序存储结构的线性表中插入或删除一个数据元素,平均约移动表中一半元素。若表长为
n
n
n,则算法 Listinsert_Sq
和 Listdelete_Sq
的时间复杂度为 O(n)
假设利用两个线性表 LA 和 LB 分别表示两个集合 A 和 B(即线性表中的数据元素即为集合中的成员),现要求一个新的集合 A = A U B。
具体见 线性表(上)
已知线性表 LA 和 LB 中的数据元素按值非递减有序排列,现要求将 LA 和 LB 归并为一个新的线性表 LC,且 LC 中的数据元素仍按值非递减有序排列。例如,设
具体见 线性表(上)
容易看出,顺序表的 “求表长” (Listlength 函数) 和 “取第 i 个数据元素” (GetElem 函数)的时间复杂度均为 O(1)
//求表长
int Listlength(List L){
return L.length;
}
//获取 L 中第 i 个元素
Status GetElem(List & L,int i, int & ele){ // i 是从 1 开始的
if ((i < 1) || (i > L.length)) return ERROR;
ele = *(L.elem + i - 1); //或写成 ele = L.elem[i - 1]
return OK;
}
又这两个例子中进行的“插入”操作均在表尾进行,则不需要移动元素
因此,例 2-1 (算法 2.1 Union) 的执行时间主要取决于査找函数 LocateElem 的执行时间。
//线性表的并集,将 Lb 合并到 La,因为 union 是关键字,所以方法名不再用 union,而用 Union
void Union (List & La, List Lb){
//将所有在线性表 Lb 中但不在 La 中的数据元素插入到 La 中
//求线性表的长度
int La_Len = Listlength (La);
int Lb_Len = Listlength (Lb);
for (int i = 1; i <= Lb_Len; i ++){
ElemType e;
//取 Lb 中第 i 个数据元素赋给 e
GetElem (Lb, i, e);
//如果 La 中没有元素和 e 相等则将 e 插入到 La 中
if (!LocateElem (La, e, equal)) //Locate elem : 定位元素
ListInsert (La, ++ La_Len, e); //La 中不存在和 e 相同的数据元素,则插入之
}
}//union
在顺序表 L 中査访是否存在和 e 相同的数据元素的最简便的方法是,令 e 和 L 中的数据元素逐个比较之,如算法 2.6 所示。
从算法 2.6 中可见,基本操作是“进行两个元素之间的比较”,
//定位线性表中使得 compare() 函数返回 true 的元素 e 的位置,其中 compare 是一个函数指针
int LocateElem(List L, ElemType e, Status(* compare)(ElemType,ElemType)){
//在顺序线性表 L 中査找第 1 个值与 e 满足 compare()的元素的位序
//若找到,则返回其在 L 中的位序,否则返回 0
ElemType * p;
//i 的初值为第 1 个元素的位序
int i = 1;
//p 的初值为第 1 个元素的存储位置
p = L.elem;
//一直找到 *p 和 e 不相等的 i 值
while(i <= L.length && !compare(*p ++,e))
++i;
//如果 i > L.length 说明 L.length = 0,则返回 0
if(i <= L.length)
return i;
else
return 0;
}//同 LocateElem_Sq
若 L 中存在和 e 相同的元素 a i a_i ai, 则比较次数为 i (1 ≤ i ≤ L. Length),否则为 L.Length,即 算法 LocateElem_Sq ( 算法 2.6 )的时间复杂度为 O(L.Length)
在 Union()
函数的循环中调用 LocateElem (La,e,equal)
,所以 LocateElem()
的时间复杂度为为 O(La.Length)
,又因为在遍历 Lb_Len 的循环中,因此,对于顺序表 La 和 Lb 而言,算法 2.1 (union)
的时间复杂度为 O(La.Length × Lb.length)
上述两个算法的时间复杂度取决于抽象数据类型 List 定义中基本操作的执行时间。因为 GetElem 的时间复杂度均为 O(1)
,LocateElem 的时间复杂度为为 O(La.Length)
,则有:
//线性表的合并
void Mergelist (List La, List Lb, List &Lc){
//已知线性表 La 和 Lb 中的数据元素按值非递减排列
//归并 La 和 Lb 得到新的线性表 Lc, Lc 的数据元素也按值非递减排列
Initlist (Lc);
//i 是 La 的元素位置,j 是 Lb 的元素位置,k 是 Lc 的元素位置,都从 1 开始
int i,j,k;
i = j = 1; k = 0;
int La_Len = Listlength(La);
int Lb_Len = Listlength (Lb);
int ai = 0;
int bj = 0;
while ((i <= La_Len) && (j <= Lb_Len)) { //La 和 Lb 均非空
//获取 La 中的第 i 个元素赋值给 ai
GetElem (La, i, ai);
//获取 Lb 中的第 j 个元素赋值给 bj
GetElem (Lb,j, bj);
//如果 ai 比 bj 小,那么把 ai 插入到 Lc 中,否则把 bj 插入到 Lc 中
if (ai <= bj) {
Listinsert (Lc, ++k, ai); ++i;
}else{
Listinsert (Lc, ++k, bj); ++j;
};
}
//后边这两个循环只执行一个
//如果 i 还没到 La 的最后,则把 La 剩下的元素 insert 到 Lc 中
while (i <= La_Len){
GetElem (La, i++, ai);
Listinsert (Lc, ++k, ai);
}
//如果 i 还没到 Lb 的最后,则把 Lb 剩下的元素 insert 到 Lc 中
while (j <= Lb_Len ) {
GetElem(Lb, j++, bj);
Listinsert(Lc, ++k, bj);
}
}// Mergelist
虽然算法 2.2 中含 3 个(while)循环语句,但只有当 i 和 j 均指向表中实际存在的数据元素时,才能取得数据元素的值并进行相互比较;并且当其中一个线性表的数据元素均已插人到线性表 LC 中后,只要将另外一个线性表中的剩余元素依次插入即可。因此,对于每一组具体的输入(LA 和 LB),后两个(while)循环语句只执行一个循环体,因为是将 La 和 Lb 插入到 Lc 中,所以 Listinsert 共执行了 Listlength (LA) + Listlength (LB)
次,即 算法 2.2 (merge)
的时间复杂度为 O (Listlength (LA) + Listlength (LB))