算法设计与分析基础(九)
练习题
习题一
a. 为一个分治算法编写伪代码,该算法同时求出一个n元素数组的最大元素 和最小元素的值。
b. 假设n = 2k,为该算法的键值比较次数建立递推关系式并求解。
c. 请将该算法与同样问题的蛮力算法做一个比较。
解答:
a. 算法思想
将数组A划分为大小大致相等的两个子数组;递归地对这两个子数组求最大元素和最小元素;将两个子数组的最大元素进行比较,返回数组A的最大元素,将其最小元素进行比较,返回数组A的最小元素;当n不超过2时,递归终止,此时最多通过一次比较就可得到最大元素与最小元素。
假设 max {a, b} , min {a, b} 分别为通过一次比较产生出两者之间的最大者与最小者的标准过程。
伪代码:
Algorithm Max-Min (A, M, N)
//输入:n个元素的数组A
//输出:数组A的最大元素M和最小元素N
begin
if n = 1 then M := A[0] and N := A[0]
else if n = 2 then M=max{A[0], A[1]} and N := min{A[0], A[1]}
else begin
B := A[0..┗n/2┛ ], C := A[ ┗n/2┛+1..n-1];
Max-Min(B, M0, N0);
Max-Min(C, M1, N1);
M := max{ M0, M1};
N := min{N0, N1};
end;
end.
b.设 T(n) 为算法求n个元素数组的最大元素和最小元素的基本运算次数,此时的基本运算为比较。当n=2时,T(2)=1,n=1时,T(1)=0 , 则n≥2时有:
c. 当n大于1时,用蛮力算法找到最大元素并取出需要n-1次比较,接着在剩下的n-1个元素中找出最小元素需要n-2次比较,总的比较次数为2n-3次,因此分治法在计算效率上比蛮力法要快。
习题二
A[0…n-1]是一个n个不同实数构成的数组。如果i < j,但是A[i] > A[j],则这对元素(A[i],A[j])被称为一个倒置(inversion)。设计一个O(nlogn)算法来计算 数组中的倒置数量。
算法思想:
对数组A实施合并排序,在排序的过程中统计元素倒置的个数。递归步骤为:
-
将A划分为两个大小基本相等的子数组B与C;
-
分别递归地对B与C进行排序并计算各自元素的倒置数;
-
对有序数组B与C进行归并, 并统计两个有序数组归并时存在的元素倒置数;
当数组A的大小不超过2时,算法递归终止、直接排序并统计倒置个数。
伪代码:
Algroithm Inversion (A[0..n-1], a)
//输入数组A[0..n-1]
//输出n个数的倒置数量a
begin
if n=1 then return (0)
else begin
B := A[0..⌊n/2⌋], C := A[⌊n/2⌋+1 .. n-1] // 将A划分成子数组B与C
Inversion (B, b); //对B进行排序并计算倒置数量b
Inversion (C, c); //对C进行排序并计算倒置数量c
Merge (B, C, A, d); //将有序数组B与C 合并成A并计算倒置数d;
a := b + c + d;
end;
end.
其中**Merge(B, C, A, d)过程中倒置数的计算依据如下:B与C中各自当前最小的元素进行比较,较小者移入A中,并对倒置数进行累加。 若较小者来自数组B,则不产生倒置;若较小者来自数组C,则将产生倒置,并且与此较小者相关的倒置数量为B中的还没有移入A的剩余元素个数。伪代码描述如下:
Procedure Merge (B[0..p-1], C[0..q-1], A[0..p+q-1], d)
// 将有序数组B与C合并成有序数组A,并计算元素的倒置数d.
begin
i: =0, j:=0, k:=0; d:=0;
while i<p and j<q do
if B[i] ≤ C[j] then A[k] :=B[i], i:=i+1//如果最小的是B产生的
else A[k] := C[j], j :=j+1, d := d + (p – i);//否则就是C产生的,倒置数是前面的B中的还没有移入A的剩余元素个数
k := k+1;
end of while
if i=p then // 数组B中的元素已经处理完毕,无需倒置追加
copy C[j..q-1] to A[k..p+q-1]
else // 数组C中的元素已经处理完毕,无需倒置追加
copy B[i..p-1] to A[0..p+q-1];
end.
复杂度分析:
由于算法是在合并排序的过程中进行倒置数的计算,从而算法的计算复杂度与合并排序复杂度相同,即为O(n log n)。
习题三
螺钉和螺姆问题
算法思想:
假设螺母和螺钉分别存放在数组Nut和Bolt中。运用快速排序的思想,在Nut中随机选择一个螺母u,通过螺母与螺钉匹配的检测,对Bolt中的螺钉进行划分,生成螺钉的子集合L*、{u*} 与 R*,其中L*中的螺钉小于螺母u,R*中的螺钉大于螺母u,而u*为与u成功匹配的螺钉。然后再用螺钉u*对Nut中的螺母进行类同的划分,生成螺母的子集合 L、{u} 与 R。这样在整个Nut和Bolt上进行匹配的问题就可继续在L与 L*、 R 与 R* 上递归进行。当子集合中只剩下一个元素时递归终止,此时的螺母和螺钉一定互相匹配。
令Partition(Nut,u*) 与 Partition(Bolt,u)为上述的划分过程,并分别返回划分后的u 与 u*,以及所在的位置 s,算法的形式化描述如下:
Algorithm Pairing (Nut[0..n-1], Bolt[0..n-1])
//输入:n个直径各不相同的螺母和n个相应的螺钉
//输出:每一对匹配的螺钉和螺母
Begin
If n = 1 then return (Nut[0], Bolt[0]);
else begin
//在Nut中随机选择一个螺母u;
(s, u*) := Partition(Bolt[0..n-1], u);
(s, u) := Partition(Nut[0..n-1], u*);
Pairing(Nut[0.. s-1], Bolt[0..s-1]);
Pairing(Nut[s+1..n-1], Bolt[s+1..n-1]);
end;
end.
时间复杂度:
算法的基本操作为元素之间的比较。算法完全在快速排序的框架下进行,在递归前仅仅增加了一次划分,而每次划分的代价为O(n),在量级上与快速排序中的划分过程的代价相同,依据快速排序算法的平均复杂度为O(nlogn) 的结果,得知上述算法的平均复杂度依然是O(nlogn)。
习题四
设计一个分治算法来计算二叉树的层数(具体来说,对于空树和单节点树, 该算法应该返回0和1)。这个算法的效率类型是怎样的?
算法思想:
对于给定的二叉树T,如果二叉树为空,则返回0;若不为空,则递归地计算其左子树的层数和右子树的层数,取其中最大者加1,即为此二叉树的层数。
伪代码:
Algorithm BinaryTree(T)
//输入:给定的二叉树T
//输出:T的层数
Begin
if T = Ø then return 0;
else return max{BinaryTree(Tleft), BinaryTree(Tright)}+1;
end.
时间复杂度:
求二叉树的高度需要遍历数的所有节点,所以该算法的时间复杂度为O(n)。
习题五
下列算法试图计算一颗二叉树的叶子数。
算法 LeafCounter(T)
//递归计算二叉树的叶子数
//输入:一颗二叉树T
//输出:T的叶子树
if T = Ø return 0
else return LeafCounter(Tleft)+LeafCounter(Trigth)
该算法正确吗?如果正确,请证明;否则请改正!
**解答:**当树为单节点树时,由题目所给的算法计算得该叶子节点数为0,所以该算法不正确。对算法的改正如下:
算法 LeafCounter (T)
//输入:一颗二叉树T
//输出:T的叶子树
if T = Ø return 0;
else if T = 1 then return 1;
else return LeafCounter(Tleft) + LeafCounter(Trigth)
习题六
为最近点对问题的一维版本(求一个给定的n整数集合中最接近的两个数)设计一个直接基于分治技术的算法,并确定它的效率类型。
解答:
算法思想:假设n整数集合S中这些整数点已经按增序排列,将这个有序划分为大小大致相等两个子集合S1、S2;递归地找出子集合S1、S2中的最近点对和一个点在S1另一点在S2的最近点对,比较上述三个点对的距离,找出最小点对;当n小于等于2时递归终止。
伪代码:
Algorithm ClosestPoints (S[0..n-1])
//输入:含n个整数的集合S
//输出:集合S中的最近点对的距离
Begin
if n = 1 then return 无穷大;
else if n = 2 return S[1]-S[0];
else return min{ ClosestPoints (S[0..┗n/2┛ ]), ClosestPoints (S[ ┗n/2┛+1..n-1]), S[┗n/2┛ +1]-S[┗n/2┛ ]}
end.
复杂度分析:
假设n=2k,基本操作为比较。T(n)=2T(n/2)+C,其中C为常数,计算得时间复杂度为O(n);若给定的数组无序,因此需要进行排序操作,此时总时间复杂度为O(nlog n)。