我希望在以下示例中理解“中位数中位数”算法:
我们有45个不同的数字,分为9组,每组5个元素。
48 43 38 33 28 23 18 13 8
49 44 39 34 29 24 19 14 9
50 45 40 35 30 25 20 15 10
51 46 41 36 31 26 21 16 53
52 47 42 37 32 27 22 17 54
递归第二步,找到中位数的“真实”中位数(50 45 40 35 30 25 20 15 10
),即该集合将分为两组:
50 25
45 20
40 15
35 10
30
对这两组进行排序
30 10
35 15
40 20
45 25
50
50 45 40 35 30 25 20 15 10
)的“真实”中位数是30,而且还有5个元素小于15,远远小于wikipedia中提到的45个元素的30% / p>
所以T(n) <= T(n/5) + T(7n/10) + O(n)
失败了。
顺便说一下,在维基百科的例子中,我得到的递归结果是36.然而,真正的中位数是47.
所以,我认为在某些情况下,这种递归可能不会返回真正的中位数中位数。我想知道我的错误在哪里。
答案 0 :(得分:33)
问题在于您要找到中位数的真实中位数的步骤。在你的例子中,你有这些中位数:
50 45 40 35 30 25 20 15 10
此数据集的真实中位数为30,而不是15.您不会通过将组拆分为五个块并取这些中位数的中位数来找到此中位数,而是通过递归调用此较小的选择算法组。逻辑中的错误是假设通过将上述序列分成两个块来找到该组的中位数
50 45 40 35 30
和
25 20 15 10
然后找到每个块的中位数。相反,中位数中值算法将在完整数据集50 45 40 35 30 25 20 15 10
上递归调用自身。在内部,这会将组拆分为五个块并对它们进行排序等,但它确定了分区步骤的分区点,并且在此分区步骤中,递归调用将找到中位数的真实中位数在这种情况下,如果你使用30作为原始算法中的分区步骤,你确实可以根据需要进行非常好的分割。
希望这有帮助!
答案 1 :(得分:26)
这是中位数算法的pseudocode算法(略微修改以适合您的示例)。维基百科中的伪代码无法描述selectIdx
函数调用的内部工作原理。
我已在代码中添加了注释以供解释。
// L is the array on which median of medians needs to be found.
// k is the expected median position. E.g. first select call might look like:
// select (array, N/2), where 'array' is an array of numbers of length N
select(L,k)
{
if (L has 5 or fewer elements) {
sort L
return the element in the kth position
}
partition L into subsets S[i] of five elements each
(there will be n/5 subsets total).
for (i = 1 to n/5) do
x[i] = select(S[i],3)
M = select({x[i]}, n/10)
// The code to follow ensures that even if M turns out to be the
// smallest/largest value in the array, we'll get the kth smallest
// element in the array
// Partition array into three groups based on their value as
// compared to median M
partition L into L1<M, L2=M, L3>M
// Compare the expected median position k with length of first array L1
// Run recursive select over the array L1 if k is less than length
// of array L1
if (k <= length(L1))
return select(L1,k)
// Check if k falls in L3 array. Recurse accordingly
else if (k > length(L1)+length(L2))
return select(L3,k-length(L1)-length(L2))
// Simply return M since k falls in L2
else return M
}
举个例子:
中位数函数的中位数将调用45个元素的整个数组,如(k = 45/2 = 22
):
median = select({48 49 50 51 52 43 44 45 46 47 38 39 40 41 42 33 34 35 36 37 28 29 30 31 32 23 24 25 26 27 18 19 20 21 22 13 14 15 16 17 8 9 10 53 54}, 45/2)
第一次调用M = select({x[i]}, n/10)
时,数组{x[i]}
将包含以下数字:50 45 40 35 30 20 15 10
。
在此调用中,n = 45
,因此select函数调用将为M = select({50 45 40 35 30 20 15 10}, 4)
第二次调用M = select({x[i]}, n/10)
时,数组{x[i]}
将包含以下数字:40 20
。
在此通话中,n = 9
因此通话将为M = select({40 20}, 0)
。
此选择调用将返回并分配值M = 20
。
现在,到了你有疑问的地步,我们现在用L
将数组M = 20
分成k = 4
。
请记住,数组L
位于:50 45 40 35 30 20 15 10
。
根据规则L1, L2
,L3
和L1 < M
,数组将分为L2 = M
和L3 > M
。因此:
L1: 10 15
L2: 20
L3: 30 35 40 45 50
自k = 4
起,它大于length(L1) + length(L2) = 3
。因此,搜索将继续进行以下递归调用:
return select(L3,k-length(L1)-length(L2))
这意味着:
return select({30 35 40 45 50}, 1)
结果将返回30。 (因为L有5个或更少的元素,因此它将返回kth中的元素,即排序数组中的第1个位置,即30)。
现在,将在第一个M = 30
函数调用中通过45个元素的整个数组接收select
,并在L
周围分隔数组M = 30
的相同分区逻辑将适用于最终获得中位数的中位数。
唷!我希望我的冗长和清晰足以解释中位数算法的中位数。