问题描述
有两个数组A和B,假设A和B已经有序(从大到小), 求A和B数组中所有数的第K大元素。
问题分析
在寻找第K
大元素时,可以使用归并排序,对两个数组进行合并,合并到第K
个元素时,即为所求值,使用此方法的时间复杂度为
O
(
K
)
O(K)
O(K),此方法并不是最优的方法,考虑使用分治法。对于任意两个元素,若
A
i
>
B
j
(1)
A_i > B_{j} \tag{1}
Ai>Bj(1)
则说明
A
i
A_i
Ai之前至少有i-1
个数,而
B
j
B_j
Bj之前至少有
i
+
j
−
1
i + j - 1
i+j−1个数,同时,当
A
i
≤
B
j
−
1
(2)
A_{i} \le B_{j - 1} \tag{2}
Ai≤Bj−1(2)
说明
A
i
A_i
Ai在合并后的数组中的位置一定为第
i
+
j
−
1
i + j - 1
i+j−1。所以在寻找第K
大元素时,若该元素在数组A中,则该满足公式(1)和公式(2)的同时,满足
i
+
j
−
1
=
K
(3)
i + j - 1 = K \tag{3}
i+j−1=K(3)
考虑若元素在数组A中时,需要寻找一对序偶 ( i , j ) (i,j) (i,j)满足公式(1)、(2)、(3)。既,我们只需要枚举 i i i的值, j j j的值可以通过公式(3)求解出,判断 A i A_i Ai是否满足(1), (2)两个不等式关系,若满足这两个关系则,该数就是我们需要寻找的第 k k k个元素。
在进行枚举时,根据大小关系,我们可以优化
i
i
i值的寻找。
若该元素不在数组A中,则一定在数组B中,所以我们先假设该元素在数组A中,在A中进行寻找,当数组A中寻找失败时,再在数组B中寻找即可。
在数组A中寻找,当
(
i
,
j
)
(i,j)
(i,j)满足(1)和(3),但是不满足(2)时,说明
B
j
B_j
Bj之前至少有
i
+
j
−
1
i + j - 1
i+j−1个数,既
B
j
B_j
Bj之前至少有
k
k
k个数,
A
i
A_i
Ai在合并后的数组中的位置为
i
−
1
<
s
i
t
e
<
k
i - 1 < site < k
i−1<site<k,也就是说
A
i
A_i
Ai比第
k
k
k个元素的数值要大,则该元素的位置一定在
i
i
i之后,在下一次寻找时,寻找区间可以缩减到
i
i
i之后。反之则K
大元素在
i
i
i之前,在前半段区间进行寻找。
流程设计
根据之前的分析,可以使用分治法,通过二分进行查找。查找区间 [ a , b ] [a,b] [a,b]的流程图如下:
使用二分法在数组A中持续寻找,最后可能没有寻找到时,返回寻找失败,流程图中返回nan
,当在数组A中寻找失败时,按照相同的方法在数组B中进行寻找,即可完成最终的任务。
使用上述算法进行寻找时,可以发现,我们的每次寻找都在逐步缩小范围,每次缩小为原来范围的一半,算法复杂度分析为 O ( 2 l o g K ) = O ( l o g K ) O(2logK) = O(logK) O(2logK)=O(logK)。将原来的线性时间复杂度缩减为对数时间复杂度。
代码实现与测试
测试两个数组(13, 10, 9, 6, 4, 2)
,(15, 11, 8, 7, 5, 3)
。实现如下:
#include<iostream>
#include<cmath>
using namespace std;
const int N = 100;
int n;
int k;
int cnt = 0;
int A[2][N];
int dfs(int al, int ar, int arr)
{
if (al > ar)
return -1;
cnt++;
int amid = (al + ar) / 2;
int bmid = k + 1 - amid;
if (A[arr][amid] >= A[!arr][bmid])
{
if (A[arr][amid] <= A[!arr][bmid - 1])
return A[arr][amid];
return dfs(amid + 1, ar, arr);
}
else
{
if (A[!arr][bmid] <= A[arr][amid - 1])
return A[!arr][bmid];
return dfs(al, amid - 1, arr);
}
}
int main()
{
A[0][0] = INT_MAX;
A[1][0] = INT_MAX;
int a[] = {13, 10, 9, 6, 4, 2};
int b[] = {15, 11, 8, 7, 5, 3 };
int La = sizeof a / sizeof (int);
int Lb = sizeof b / sizeof(int);
memcpy_s(A[0] + 1, sizeof a, a, sizeof a);
memcpy_s(A[1] + 1, sizeof b, b, sizeof b);
while (cin >> k)
{
cnt = 0;
int ans = dfs(1, min(k, La + 1), 0);
if (ans == -1)
{
//不在数组A里面
ans = dfs(1, min(k, Lb + 1), 1);
}
cout << ans << endl;
cout << "寻找次数: " << cnt << endl;
}
return 0;
}
测试结果为