如何在 O(n) 时间内找到与 n 个不同数字的中位数最近的 k 个邻居?

Posted

技术标签:

【中文标题】如何在 O(n) 时间内找到与 n 个不同数字的中位数最近的 k 个邻居?【英文标题】:How to find k nearest neighbors to the median of n distinct numbers in O(n) time? 【发布时间】:2010-12-06 04:09:26 【问题描述】:

我可以使用中位数选择算法在 O(n) 中找到中位数。另外,我知道算法完成后,中位数左侧的所有元素都小于中位数,右侧的所有元素都大于中位数。但是如何在 O(n) 时间内找到离中位数最近的 k 个邻居?

如果中位数为n,则左边的数字小于n,右边的数字大于n。 但是,数组不是在左侧或右侧排序的。这些数字是用户给出的任何一组不同的数字。

问题来自Cormen的Introduction to Algorithms,问题9.3-7

【问题讨论】:

如果中位数在位置 n,您是在寻找位置 n+1 和位置 n-1 的值吗? 数字是大数还是定点整数? 【参考方案1】:

似乎没有人完全拥有这一点。这是如何做到的。首先,如上所述找到中位数。这是 O(n)。现在将中位数停在数组的末尾,并从每个其他元素中减去中位数。现在再次使用快速选择算法找到数组的元素 k(不包括最后一个元素)。这不仅会找到元素 k(按顺序),它还会离开数组,使最小的 k 数字位于数组的开头。这些是最接近中位数的 k,一旦将中位数重新添加进去。

【讨论】:

我猜你应该在找到第 k 阶统计量之前取数字模数【参考方案2】:

中位数的中位数可能对找到最近的邻居没有多大帮助,至少对于较大的 n 而言。诚然,每列 5 列围绕它的中位数进行分区,但这不足以解决问题。

我只是将中位数视为中间结果,并将最近的邻居视为优先队列问题...

从中位数中获得中位数后,请记下它的值。

对所有数据运行 heapify 算法 - 请参阅 Wikipedia - Binary Heap。在比较中,结果基于相对于保存的中值的差异。优先级最高的项目是那些具有最低 ABS(值 - 中值)的项目。这需要 O(n)。

数组中的第一项现在是中位数(或它的副本),并且数组具有堆结构。使用堆提取算法根据需要提取尽可能多的最近邻居。对于 k 个最近的邻居,这是 O(k log n)。

只要 k 是一个常数,你就会得到 O(n) 中位数的中位数,O(n) heapify 和 O(log n) 提取,总得 O(n)。

【讨论】:

heapify的复杂度不是O(nlogn)吗? 如果你用愚蠢的方式(将每个项目依次插入最初的空堆)它是 O(n log n)。如果使用 heapify 算法,则为 O(n)。有关更多详细信息,请参阅***页面(“构建堆”部分)。 为什么我们可以把 k 当作一个常数?如果k == n怎么办? @Yos - 首先,当指定算法的复杂性时,除非另有说明,否则k 按照惯例被假定为独立于n 的某个常数。此外,在按惯例称为“k 个最近邻居”的问题中,k 始终表示要查找的邻居的数量,它始终是恒定的(至少在独立于其他有界的意义上- 按顶点总数n)。这并非巧合 - 有一个更广泛的约定,k 代表一些常数,独立于其他变量。【参考方案3】:
med=Select(A,1,n,n/2)   //finds the median

for i=1 to n
   B[i]=mod(A[i]-med)

q=Select(B,1,n,k) //get the kth smallest difference

j=0
for i=1 to n
   if B[i]<=q 
     C[j]=A[i] //A[i], the real value should be assigned instead of B[i] which is only the difference between A[i] and median.
       j++
return C

【讨论】:

因为数组 B 中的值可以相等,所以你应该确保 j 不大于 k。同时,如果你用文字描述你的答案,其他人可能会更好地理解你。【参考方案4】:

你可以这样解决你的问题:

你可以在 O(n) 中找到中位数,w.g.使用 O(n) nth_element 算法。

你循环遍历所有元素,用一对替换每个元素:

the absolute difference to the median, element's value. 

再一次用 n = k 做 nth_element。应用此算法后,您可以保证在新数组中首先具有绝对差的 k 个最小元素。你把他们的索引和完成!

【讨论】:

这与@HalPri 的答案相同,该答案比您早一年发布。 这比@HalPri 的回答要好——@Shivendra 使用的是absoulte difference,这解决了我在对@HalPri 的回答的评论中指出的问题【参考方案5】:

四个步骤:

    使用Median of medians 定位数组的中位数 - O(n) 确定中位数和数组中每个元素之间的绝对差并将它们存储在一个新数组中 - O(n) 使用 Quickselect 或 Introselect 从新数组中挑选出 k 个最小元素 - O(k*n) 通过索引原始数组检索 k 个最近邻 - O(k)

当k足够小时,整体时间复杂度变成O(n)。

【讨论】:

【参考方案6】:
    求 O(n) 中的中位数。 2. 创建一个新数组,每个元素是原始值的绝对值减去中位数 3. 在 O(n) 中找到第 k 个最小的数 4. 期望的值是与中位数的绝对差小于或等于新数组中第 k 个最小的数。

【讨论】:

【参考方案7】:

您可以在数字列表L 上使用非比较排序,例如基数排序,然后通过考虑 k 个元素的窗口并检查窗口端点来找到 k 个最近的邻居。另一种表述“查找窗口”的方式是查找 i,它最小化 abs(L[(n-k)/2+i] - L[n/2]) + abs(L[(n+k)/2+i] - L[n/2])(如果 k 是奇数)或 abs(L[(n-k)/2+i] - L[n/2]) + abs(L[(n+k)/2+i+1] - L[n/2])(如果 k 是偶数)。结合案例,abs(L[(n-k)/2+i] - L[n/2]) + abs(L[(n+k)/2+i+!(k&amp;1)] - L[n/2])。找到最小值的一种简单的 O(k) 方法是从 i=0 开始,然后向左或向右滑动,但您应该能够在 O(log(k)) 中找到最小值。

您最小化的表达式来自将L 转换为另一个列表M,方法是获取每个元素与中值的差异。

m=L[n/2]
M=abs(L-m)

i 最小化M[n/2-k/2+i] + M[n/2+k/2+i]

【讨论】:

【参考方案8】:

你已经知道如何在 O(n) 中求中位数

如果顺序无关紧要,可以在 O(n) 中完成 k 最小的选择 求中位数的右轴最小的k,中位数的左轴最大的k

from wikipedia

 function findFirstK(list, left, right, k)
 if right > left
     select pivotIndex between left and right
     pivotNewIndex := partition(list, left, right, pivotIndex)
     if pivotNewIndex > k  // new condition
         findFirstK(list, left, pivotNewIndex-1, k)
     if pivotNewIndex < k
         findFirstK(list, pivotNewIndex+1, right, k)

不要忘记 k==n 返回原始列表的特殊情况

【讨论】:

【参考方案9】:

其实答案很简单。我们需要做的就是选择与中位数绝对差最小的 k 个元素,当中位数在索引 m 处时,中位数从 m-1 移动到 0 和 m+1 到 n-1。我们使用与合并 2 个排序数组相同的想法来选择元素。

【讨论】:

但是考虑到元素不是根据它们与中位数的绝对差值排序的,我们如何在 O(n) 中选择它们?【参考方案10】:

所有建议从数组中减去中位数的答案都会产生不正确的结果。此方法将查找值最接近的元素,而不是位置最接近的元素。

例如,如果数组是1,2,3,4,5,10,20,30,40。对于 k=2,返回的值为 (3,4);这是不正确的。正确的输出应该是 (4,10),因为它们是最近的邻居。

找到结果的正确方法是使用选择算法来查找上限和下限元素。然后通过直接比较从列表中找到剩余的元素。

【讨论】:

【参考方案11】:

如果你知道中位数的索引,可能只是ceil(array.length/2),那么它应该是一个列出n(xk),n(x-k+1)的过程, ... , n(x), n(x+1), n(x+2), ... n(x+k) 其中 n 是数组,x 是中位数的索引,k 是您需要的邻居数。(可能是 k/2,如果您想要总 k,而不是每边 k)

【讨论】:

这不起作用。中位数算法的中位数不会对项目进行排序。这样做需要 O(n log n),而中位数的中位数则需要 O(n)。 啊,抱歉。我在第 2 版中阅读了原始问题,他补充说他已经按顺序对其进行了排序。【参考方案12】:

首先选择O(n) 时间的中位数,使用该复杂度的standard algorithm。 然后再次遍历列表,选择最接近中位数的元素(通过存储最知名的候选者并将新值与这些候选者进行比较,就像搜索最大元素一样)。

在这个额外的遍历列表的每一步中,需要 O(k) 步,因为 k 是常数,所以这是 O(1)。因此,额外运行所需的总时间为 O(n),完整算法的总运行时间也是如此。

【讨论】:

虽然当 k 为常数时 O(k) 为 O(1),但如果 k -> n 则变为 O(n^2)。另外,你怎么知道 k 是常数?如果是,那么n不能也被认为是常数吗?【参考方案13】:

由于所有元素都是不同的,因此最多可以有 2 个元素与平均值的差值相同。我认为拥有 2 个数组 A[k] 和 B[k] 表示与平均值之差的绝对值对我来说更容易。现在的任务是通过在 A[i+1] 和 B[i+1] 之前读取读取 A[i] 和 B[i] 的数组的前 k 个非空值来填充数组并选择 k 个元素。这可以在 O(n) 时间内完成。

【讨论】:

“通过读取数组的前 k 个非空值来选择 k 个元素”——为此,必须对数组进行排序。对这些数组进行排序需要时间 O(n log n)。 @Windows 程序员:仅当您进行基于比较的排序时。

以上是关于如何在 O(n) 时间内找到与 n 个不同数字的中位数最近的 k 个邻居?的主要内容,如果未能解决你的问题,请参考以下文章

在 O(n) 时间内找到数组中的重复元素

我们如何在 O(n) 时间和 O(1) 空间复杂度内找到数组中的重复数字

找到具有 O(1) 空间和 O(n) 时间的重复数字

找到给定范围内数字的最大最小差

设计一个 O(n) 算法来找到一个不在 [0,n-1] 范围内的数字 [重复]

在 O(n) 时间内找到数组中的 10 个最大整数