【问题标题】:Tricky Median Question棘手的中位数问题
【发布时间】:2011-10-26 22:24:18
【问题描述】:

给定n个点,在给定列表中选择一个点,使得到该点的距离总和与其他所有点相比最小。

距离的测量方式如下。

对于一个点 (x,y),所有 8 个相邻点的距离均为 1。

(x+1,y)(x+1,y+1),(x+1,y-1),(x,y+1),(x,y-1),(x-1,y)(x-1,y+1),(x-1,y-1)

编辑

更清晰的解释。

函数 foo 定义为

foo(point_a,point_b) = max(abs(point_a.x - point_b.x),abs(point_a.y - point_b.y))

找到一个点 x 使得 sum([foo(x,y) for y in list_of_points]) 最小。

例子

输入:

12 -14
-3 3
-14 7
-14 -3
2 -12
-1 -6

输出

-1 -6

例如: (4,5) 和 6,7) 之间的距离为 2。

这可以通过检查每对的总和在 O(n^2) 时间内完成。 有更好的算法吗?

【问题讨论】:

  • 仅供参考:此问题的克隆发布在Programming puzzles and Code golf。
  • 看起来像 smalles circle problem 的变体,即 O(n)
  • 这是 interviewstreet.com 上一个问题的副本——我不确定运行它的人是否会喜欢这个。
  • 顺便说一句,这不叫'median',它叫'Fermat point'。我们想在最接近它的列表中找到那个点。

标签: python algorithm


【解决方案1】:

更新:有时无法找到最佳值,我会留在这里,直到找到问题为止。

这是O(n):nth 是 O(n)(预期的,不是最差的),遍历列表是 O(n)。如果您需要严格的 O(),则选择中间元素进行排序,但它将是 O(n*log(n))。

注意:修改它以返回所有最佳点很容易。

import sys

def nth(sample, n):
    pivot = sample[0]
    below = [s for s in sample if s < pivot]
    above = [s for s in sample if s > pivot]
    i, j = len(below), len(sample)-len(above)
    if n < i:      return nth(below, n)
    elif n >= j:   return nth(above, n-j)
    else:          return pivot

def getbest(li):
    ''' li is a list of tuples (x,y) '''
    l = len(li)
    lix = [x[0] for x in li]
    liy = [x[1] for x in li]

    mid_x1 = nth(lix, l/2) if l%2==1 else nth(lix, l/2-1)
    mid_x2 = nth(lix, l/2)
    mid_y1 = nth(liy, l/2) if l%2==1 else nth(liy, l/2-1)
    mid_y2 = nth(liy, l/2)

    mindist = sys.maxint
    minp = None
    for p in li:
        dist = 0 if mid_x1 <= p[0] <= mid_x2 else min(abs(p[0]-mid_x1), abs(p[0]-mid_x2))
        dist += 0 if mid_y1 <= p[1] <= mid_y2 else min(abs(p[1]-mid_y1), abs(p[1]-mid_y2))
        if dist < mindist:
            minp, mindist = p, dist
    return minp

它基于一维问题的解决方案 - 对于一个数字列表,找到一个总和距离最小的数字。

如果列表中有偶数个元素,则解决方案是(排序的)列表的中间元素或两个中间元素(包括这两个元素)之间的任意数字。

更新:我的 nth 算法似乎很慢,可能有更好的方法来重写它,sort 在 sort(lix); sort(liy); 和

def nth(sample, n):
    return sample[n]

对于想要测试他的解决方案的任何人,这就是我使用的。只需运行一个循环,生成输入并将您的解决方案与 bruteforce 的输出进行比较。

import random
def example(length):
    l = []
    for x in range(length):
        l.append((random.randint(-100, 100), random.randint(-100,100)))
    return l

def bruteforce(li):
    bestsum = sys.maxint
    bestp = None
    for p in li:
        sum = 0
        for p1 in li:
            sum += max(abs(p[0]-p1[0]), abs(p[1]-p1[1]))
        if sum < bestsum:
            bestp, bestsum = p, sum
    return bestp

【讨论】:

  • 第 n 个 O(n) 怎么样?预计会递归固定次数吗?
  • 否,但处理后的列表呈指数级缩小。 en.wikipedia.org/wiki/…
  • 可爱,谢谢。我即将发布一个固定的“脑死亡”版本。你能再看一遍吗?
  • mid_x1 &lt;= p[0] &lt;= mid_x1 有什么意义?应该只是p[0] == mid_x1。另外你能进一步解释getbest吗?我不明白你在做什么。
  • @yi_H 由于考虑的指标是 Dinf,dist 不应该是到中位数集的 x 和 y 距离的最大值而不是它们的总和吗?
【解决方案2】:

我可以想象一个比 O(n^2) 更好的方案,至少在常见情况下是这样。

从您的输入点构建一个quadtree。对于树中的每个节点,计算该节点内点的数量和平均位置。然后对于每个点,您可以使用四叉树在小于 O(n) 的时间内计算其到所有其他点的距离。如果您正在计算从点 p 到远处四叉树节点 v 的距离,并且 v 不与 p 的 45 度对角线重叠,那么从 p 到 v 中所有点的总距离很容易计算(对于 v与 p 相比,它们在水平方向上比在垂直方向上分开更多,它只是v.num_points * |p.x - v.average.x|,如果 v 主要是垂直分开的,则类似地使用 y 坐标)。如果 v 与 45 度对角线之一重叠,则对其分量进行递归。

至少当你能找到一个平衡的四叉树来表示你的点时,这应该超过 O(n^2)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-07
    • 2013-02-08
    相关资源
    最近更新 更多