我可以使用中位数选择算法的中位数来找到 O(n) 中的中位数。另外,我知道算法完成后,中位数左侧的所有元素都小于中位数,右侧的所有元素都大于中位数。但是如何在 O(n) 时间内找到离中位数最近的 k 个邻居?
如果中位数为n,则左边的数字小于n,右边的数字大于n。但是,数组不是在左侧或右侧排序的。这些数字是用户给出的任何一组不同的数字。
问题来自 Cormen 的算法介绍,问题 9.3-7
我可以使用中位数选择算法的中位数来找到 O(n) 中的中位数。另外,我知道算法完成后,中位数左侧的所有元素都小于中位数,右侧的所有元素都大于中位数。但是如何在 O(n) 时间内找到离中位数最近的 k 个邻居?
如果中位数为n,则左边的数字小于n,右边的数字大于n。但是,数组不是在左侧或右侧排序的。这些数字是用户给出的任何一组不同的数字。
问题来自 Cormen 的算法介绍,问题 9.3-7
似乎没有人完全拥有这一点。这是如何做到的。首先,如上所述找到中位数。这是 O(n)。现在将中位数停在数组的末尾,并从每个其他元素中减去中位数。现在再次使用快速选择算法找到数组的元素 k(不包括最后一个元素)。这不仅会找到元素 k(按顺序),它还会离开数组,使最小的 k 数字位于数组的开头。这些是最接近中位数的 k,一旦您将中位数加回。
中位数的中位数可能对找到最近的邻居没有多大帮助,至少对于较大的 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)。
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
你可以这样解决你的问题:
您可以使用 O(n) nth_element 算法找到 O(n), wg 中的中位数。
您遍历所有元素,用一对替换每个元素:
the absolute difference to the median, element's value.
再一次用 n = k 做 nth_element。应用此算法后,您可以保证在新数组中首先具有绝对差的 k 个最小元素。你把他们的指数和完成!
四个步骤:
当 k 足够小时,整体时间复杂度变为 O(n)。
您已经知道如何在 O(n) 中找到中位数
如果顺序无关紧要,可以在 O(n) 中选择 k 最小值
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 返回原始列表的特殊情况
您可以在 numbers 列表上使用非比较排序,例如基数排序,L
然后通过考虑 k 个元素的窗口并检查窗口端点来找到 k 个最近邻居。另一种表述“找到窗口”的方式是找到最小化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 是偶数)的 i。结合案例,abs(L[(n-k)/2+i] - L[n/2]) + abs(L[(n+k)/2+i+!(k&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]
。
其实,答案很简单。我们需要做的就是选择与中位数绝对差最小的 k 个元素,当中位数在索引 m 处时,中位数从 m-1 移动到 0 和 m+1 到 n-1。我们使用与合并 2 个排序数组相同的想法来选择元素。
如果您知道中位数的索引,可能只是 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)
及时选择中位数。然后再次遍历列表,选择最接近中位数的元素(通过存储最知名的候选者并将新值与这些候选者进行比较,就像搜索最大元素一样)。
在此额外运行的每个步骤中,需要 O(k) 个步骤,并且由于 k 是恒定的,因此这是 O(1)。因此,额外运行所需的总时间为 O(n),完整算法的总运行时间也是如此。
由于所有元素都是不同的,因此最多可以有 2 个元素与平均值的差异相同。我认为拥有 2 个数组 A[k] 和 B[k] 表示与平均值之差的绝对值对我来说更容易。现在的任务是通过在 A[i+1] 和 B[i+1] 之前读取 A[i] 和 B[i] 的数组的前 k 个非空值来填充数组并选择 k 个元素。这可以在 O(n) 时间内完成。
所有建议从数组中减去中位数的答案都会产生不正确的结果。此方法将查找值最接近的元素,而不是位置最接近的元素。
例如,如果数组是1,2,3,4,5,10,20,30,40
. 对于 k=2,返回的值为 (3,4);这是不正确的。正确的输出应该是 (4,10),因为它们是最近的邻居。
找到结果的正确方法是使用选择算法来查找上限和下限元素。然后通过直接比较从列表中找到剩余的元素。