【问题标题】:Find pairs of elements in order in terms of distance根据距离按顺序查找元素对
【发布时间】:2020-09-09 18:03:54
【问题描述】:

假设我们有数据 a₁, ..., aₙ,其中 n 是偶数并且每个 aᵢ ∈ ℝ。还要定义两对元素之间的距离 dis(aᵢ, aⱼ) = | aᵢ - aⱼ |。现在程序应该输出一个按距离升序排序的元素对列表。此外,程序应该将输入数据打包成对,因此每个元素 aᵢ 在输出中只会出现一次。

例如,给定输入 [1, 0.4, 3, 1.1],输出应为 [(1, 1.1), (0.4, 3)]。

一种简单的蛮力方法是计算所有C(n,2)对并排序每对的距离。

def not_in_list_of_pair(i, ls):
    return not i in [p[0] for p in ls] + [p[1] for p in ls]

def calc(ls):
    ls = sorted(ls)
    d ={}
    for idx1, i in enumerate(ls[:-1]):
        for idx2, j in enumerate(ls[idx1+1:], idx1 + 1):
            d[(i,j)] = j - i
            
    # 2nd part
    res = []
    for pair in sorted(d, key = lambda k: d[k]):
        i, j = pair
        if not_in_list_of_pair(i, res) and not_in_list_of_pair(j, res):
            res.append(pair)
    return res

# another example
ls = [1, 0.1, 2, 2.4, 3, 4, 1.5]
assert calc(ls) == [(2, 2.4), (1, 1.5), (3, 4)]

但是这种幼稚的方法仅适用于 O(n²),并且第二部分(提取最小距离)也很慢。因此,我正在寻找一种更有效的方法来解决这个问题。谢谢!

【问题讨论】:

  • 计算两点之间的曼哈顿距离
  • @deadshot 很抱歉造成混乱。本来以为绝对值距离是曼哈顿距离的特例。
  • 您必须检查每个组合,没有其他选择。您可以做的一件事是使用set 而不是list
  • 感觉这在 O(n log n) 时间内应该是可行的,但是在不循环扫描列表的情况下将其全部完成会有点技巧。
  • 排序是 O(n log n),并且按排序顺序获得成对距离感觉就像在 O(n记录 n)。

标签: python list distance


【解决方案1】:

我不得不说你对问题的描述不清楚,描述的复杂性也不正确,即你必须计算所有整数对的距离(即O(n^2))然后对所有距离进行排序(即 O(n^2 * log(n^2)))。

对于这个问题,你基本上是找到两个距离最小的整数,把这两个整数挑出来,然后对剩下的整数重复同样的过程。

一个天真的解决方案是,假设整数是排序,我们只找到距离最小的一对整数,那么我们只需要计算每个整数的距离两个 相邻 整数(例如,ls[0]ls[1] 之间的距离,ls[1]ls[2] 之间的距离,...,ls[n - 2]ls[n - 1] 之间的距离)并找出哪一对是最小的。我们找到一个之后,去掉选中的两个整数,剩下的整数还是排序的。如果我们想找到下一个距离最小的整数对,问题还是一样的。

朴素的解决方案在两个方面仍然很昂贵:(1)我们每次需要计算每两个相邻整数的距离; (2) 我们需要从一个有序数组中取出两个整数,并保持数组有序。

为了解决(1),实际上我们不必每次都计算所有的距离。例如,假设我们有 6 个整数,计算 dist(0, 1)、dist(1, 2)、dist(2, 3)、dist(3, 4)、dist(4, 5)。我们发现第 2 个和第 3 个整数是最接近的整数,所以我们输出并删除第 2 个和第 3 个整数。对于下一轮,我们需要计算 dist(0, 1), dist(1, 4), dist(4, 5)。我们可以看到我们只需要删除 dist(1, 2) 和 dist(3, 4) 因为它们没有用,但是我们需要添加一个新的距离 dist(1, 4) 而 dist(0, 1) 和dist(4, 5) 没有改变。我们可以维护一个btree来达到目的。

为了解决(2),我们可以从中间删除项目的最佳数据结构是复杂度 O(1) 的双链表。但是我们现在正在使用数组,我们可能不想将数组更改为链表。一种方法是我们使用索引数组来模拟一个双链表。

这是一个例子。

更新 1:我发现 OrderedDict 每次都不会弹出最小项。我在 python 中找不到任何可用作 btree 的数据结构。我必须使用一个无法删除那些无用距离但我可以识别并忽略它们的堆。抱歉弄错了。

更新 2:在while 循环中添加一个else 分支,即我们不应该在看到无用项时更改双链表。

更新 3:请注意,在 while 循环的每次迭代中,堆不会超过 n 项。所以复杂度大约是O(n log n),其中n是整数的个数。

from heapq import *

def calc(ls):
  ls = sorted(ls) # O(nlogn)
  n = len(ls)

  # mimic a double linked list
  left = [i - 1 for i in range(n)]
  right = [i + 1 for i in range(n)]
  appeared = [False for i in range(n)]

  btree = []
  for i in range(0, n - 1):
    # distance of adjacent integers, and their indices
    heappush(btree, (ls[i + 1] - ls[i], i, i + 1))

  # roughly O(n log n), because the heap will have at most `n` items in each iteration
  result = []
  while len(btree) != 0:
    minimal = heappop(btree)
    a, b = minimal[1:3]

    # skip if either a or b appeared
    if not appeared[a] and not appeared[b]:
      result.append((ls[a], ls[b]))
      appeared[a] = True
      appeared[b] = True
    else:
      continue # this is important
    #print result

    if left[a] != -1:
      right[left[a]] = right[b]
    if right[b] != n:
      left[right[b]] = left[a]
    if left[a] != -1 and right[b] != n:
      heappush(btree, (ls[right[b]] - ls[left[a]], left[a], right[b]))

  return result

ls = [1, 0.1, 2, 2.4, 3, 4, 1.5]
print calc(ls)

输出如下:

[(2, 2.4), (1, 1.5), (3, 4)]
Note: The number of input integers is 7, which is NOT even.

再显示一张图片来展示正在发生的事情:

我对Python不是很熟悉,所以上面的代码sn-p中我可能没有使用最好的数据结构。

【讨论】:

  • 我认为这应该是 O(n log n)。 (假设sorted() 是 O(n log n),无论它是否正确,这可能是一个可以接受的假设。)
猜你喜欢
  • 2021-09-23
  • 2018-05-18
  • 2021-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
相关资源
最近更新 更多