按距离顺序查找元素对

问题描述

假设我们有数据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²),第二部分(提取最小距离)也很慢。因此,我正在寻找一种更有效的方法解决此问题。谢谢!

解决方法

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

对于这个问题,您基本上是在寻找距离最小的两个整数,挑选出这两个整数,并对其余整数重复相同的过程。

一个幼稚的解决方案是,假设整数是 sorted ,而我们只找到一对距离最小的整数,那么我们只需计算每个距离两个相邻整数(例如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)。我们发现第二和第三整数是壁橱的整数,因此我们输出并删除了第二和第三整数。对于下一轮,我们需要计算dist(0,1),dist(1,4),dist(4,5)。我们可以看到我们只需要删除dist(1,2)和dist(3,4)即可,因为它们没有用,但是我们需要在dist(0,1)和dist(0,1)和dist(4,5)不变。我们可以维护一个btree来达到目的。

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

这里是一个例子。

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

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

更新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.

再显示一张图像以显示发生了什么事情: enter image description here

我对Python不太熟悉,因此上面的代码片段中可能没有使用最好的数据结构。