算法概论作业2.3


#Ex.2.22

给定两个有序表,大小分别为m和n。给出一个算法,以$O(log m+log n)$的时间找出两个列表合并后的有序列表中的第K小元素


我们从最基础的情况开始考虑,假设一个数组为空,在另一个数组中找第K小元素,很明显,如果k不超过第二个数组的长度,那么结果就是B[bLeft+k-1]
反之同理
所以,合法性判定,和终止判定如下

1
2
3
4
5
6
7
8
9
10
11
if (k>(aRight - aLeft + 1 + bRight - bLeft + 1))
{
cerr << "输入不合法" << endl;
return -1;
}
if (aLeft > aRight){
return B[bLeft+ k - 1];
}
if (bLeft> bRight){
return A[aLeft+ k - 1];
}

下面,我们回归一般情况下
我们取

1
2
int aMiddle = (aLeft + aRight) / 2;
int bMiddle = (bLeft + bRight) / 2;

首先比较$A[aMiddle]$与$B[bMiddle]$的大小
我们不妨假设$A[aMiddle]>B[bMiddle]$

如果满足$A[aMiddle]>B[bMiddle]$

那么必定有$A[aMiddle+1,…,aRight]$排在所有元素的$(aMiddle+bMiddle)$之后
同时还有$B[bLeft,…,bMiddle]$排在所有元素的$(aMiddle+bMiddle)$之前

然后那么如果

1
k <= (aMiddle-aLeft)+(bMiddle-bLeft)+1

我们可以把A数组右边的部分舍弃,否则
我们可以把B数组的左边部分舍弃,并在剩余部分寻找第(k-舍弃部分的个数)

反之亦然。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (A[aMiddle] > B[bMiddle])
{
if (k <= (aMiddle - aLeft) + (bMiddle - bLeft) + 1)
FindElement(A, B, aLeft, aMiddle - 1, bLeft, bRight, k);
else
FindElement(A, B, aLeft, aRight, bMiddle + 1, bRight, k - (bMiddle - bLeft) - 1);
}
else
{
if (k <= ((aMiddle - aLeft) + (bMiddle - bLeft) + 1))
FindElement(A, B, aLeft, aRight, bLeft, bMiddle - 1, k);
else
FindElement(A, B, aMiddle + 1, aRight, bLeft, bRight, k - (aMiddle - aLeft) - 1);
}

全部代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<iostream>
using namespace std;

int FindElement(int A[], int B[], int aLeft, int aRight, int bLeft, int bRight, int k);
int main()
{
int A[10] = { 1, 3, 5, 6, 7, 9, 11, 13, 15, 17 };
int B[5] = { 2, 4, 8, 10, 12, };
//int k;
//cout << "请输入k的值:";
//cin >> k;

for (int k = 1; k < 10; k++)
cout << endl << FindElement(A, B, 0, 9, 0, 4, k);
return 0;
}
int FindElement(int A[], int B[], int aLeft, int aRight, int bLeft, int bRight, int k)
{
if (k > (aRight - aLeft + 1 + bRight - bLeft + 1))
{
cerr << "输入不合法" << endl;
return -1;
}
if (aLeft > aRight){
return B[bLeft + k - 1];
}
if (bLeft > bRight){
return A[aLeft + k - 1];
}
int aMiddle = (aLeft + aRight) / 2;
int bMiddle = (bLeft + bRight) / 2;
if (A[aMiddle] > B[bMiddle])
{
if (k <= (aMiddle - aLeft) + (bMiddle - bLeft) + 1)
FindElement(A, B, aLeft, aMiddle - 1, bLeft, bRight, k);
else
FindElement(A, B, aLeft, aRight, bMiddle + 1, bRight, k - (bMiddle - bLeft + 1));
}
else
{
if (k <= ((aMiddle - aLeft) + (bMiddle - bLeft) + 1))
FindElement(A, B, aLeft, aRight, bLeft, bMiddle - 1, k);
else
FindElement(A, B, aMiddle + 1, aRight, bLeft, bRight, k - (aMiddle - aLeft + 1));
}
}

#Ex2.27
(a)

\begin{equation}
\left[
\begin{array}{ccc}
a & b \
d & e
\end{array}
\right]
^2=
\left[
\begin{array}{ccc}
a^2+bc & b(a+d) \
c(a+d) & bc+d^2
\end{array}
\right]
\end{equation}

b)
因为分治之后得到的子问题不再是平方操作了,所以该算法的运行时间不是$O(nlog25)$

#Ex2.31

算法:
$gcd = 2gcd(a/2,b/2)$, 若a,b都是偶数
$gcd(a,b/2)$, 若a是奇数,b是偶数
$gcd(|a-b|,Min(a,b))$,若a,b都是奇数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int gcd(int a, int b)
{
if(a == 0) return b;
if(b == 0) return a;
if(a % 2 == 0 && b % 2 == 0) return 2 * gcd(a/2, b/2);
else if(a % 2 == 0) return gcd(a/2 , b);
else if(b % 2 == 0) return gcd(a, b/2);
else return gcd(abs(a - b), Min(a, b));
}

int Min(int a,int b)
{
if (a >= b)
{
return b;
}
else
return a;
}