【问题标题】:Ordering an array for maximal pairwise matches为最大成对匹配排序数组
【发布时间】:2011-02-06 06:46:05
【问题描述】:

我有一个数组:

array([[ 4, 10],
       [ 4,  2],
       [ 0,  7],
       [ 5, 11],
       [ 6,  8],
       [ 3,  6],
       [ 9,  7],
       [ 2, 11],
       [ 9,  5],
       [ 8,  1]])

我想要一种方法来对值对进行排序,以便尽可能多的成对 2 元素集具有共同的值。这是所需有序数组的示例:

array([[ 4, 10],
       [ 4,  2],
       [ 2, 11],
       [ 5, 11],
       [ 9,  5],
       [ 9,  7],
       [ 0,  7],  #note the gap here:
       [ 8,  1],
       [ 6,  8],
       [ 3,  6]])

关于这些数组有几个条件成立。没有重复的对(即:如果 [0,1] 已经存在,则 [1,0] 或 [0,1] 将出现在数组的其他位置)。没有一对具有相同的值(即:[1,1] 将不存在)。没有一对将有两个以上的匹配项(iow:整个数组中没有任何值存在超过两次。)但是一对可以有少至零个匹配项(请注意在上面的数组中没有匹配项的间隙)。

显然,我可以创建数组的每个排列,但这似乎很野蛮。我认为可能有某种方法可以切割甲板并以合乎逻辑的方式重新堆叠,这样它就可以在少量切割中进行分类。但在我走这条路之前,我想: 1) 确保没有 numpycollections 函数已经这样做了。 2) 知道使用 numpy .sort() (或类似的)没有棘手的天才方法来做到这一点。 3)找出这是否是一个常见的任务,并且有算法可以做到这一点。 (“哦,这就是 Blumen-Funke 算法!”)

这里有一些代码可以生成混洗的测试数组并检查排序的数组:

def shuffled(N=12, ans=1):
    '''returns is now the unsorted test array'''
    r = range(N)
    random.shuffle(r)
    l = []
    for i in range(N):
        l.append((r[i-1], r[i]))
    random.shuffle(l)
    return np.array(l)[ans+1:]

# step 2: ???

def test_ordered(a):
    '''checks if generated array has been sorted'''
    c0 = a[1:,0]==a[:-1,0]
    c1 = a[1:,0]==a[:-1,1]
    c2 = a[1:,1]==a[:-1,0]
    c3 = a[1:,1]==a[:-1,1]
    cond = c0+c1+c2+c3
    ans = sum(numpy.logical_not(cond))
    # when sorted this should return the same number input into
    # shuffled() as 'ans':
    return ans

(这是一个主观问题吗?有人警告我是。)

结果:

非常感谢您的帮助。 Sven 的解决方案比 Paul 的解决方案快 20% 左右,而且令人高兴的是,它们都在线性时间内运行,Doug 的回答并没有解决问题。性能对输入数据中“中断”或“间隙”的数量存在很高但也很大程度上是线性的依赖关系。见下图。 10,000 量级轴是 N。0.5 轴是中断的百分比。 z 轴是以秒为单位的性能。

再次感谢!

【问题讨论】:

  • 这并不是真正的“排序”,而是想出“多米诺骨牌”对的[最长]运行。使用适当的算法。排序更经常然后不需要在排序输入中的任何两个值之间定义独立排序。然而,上面请求的排序依赖于其他值。我不确定 Blumen-Funke 算法,但这可以使用pigeon-hole principle 解决——快乐编码。
  • 不是主观的。科学话语中对“最佳”的定义非常狭窄,在这种情况下,这意味着“我想要一种对其进行排序的方法,以便尽可能多的成对 2 元素集具有共同值。”
  • OTH,我同意@pst,我认为您的输出中的任何两对都没有自然排序,因此您可能需要改写您的问题。您正在寻找类似一组分组的东西,对吗?还是这里有我看不到的订单?
  • 您的输入是一个无向图 - 每对都是一条不同的边。您正在寻找一种方法来尽可能少地“举起笔”来绘制它。不知道解决方案,但我想贪婪地消耗欧拉行走是一个好的开始......
  • @Wong 由于数据的限制,没有pair会有两个以上的匹配,它产生了一组1D图,在某种意义上是有顺序的(倒序是等价的)跨度>

标签: python arrays numpy


【解决方案1】:

您已经描述了一个图,其中顶点是数字,边是您的对。

您的条件指定每个数字在列表中出现一次或两次。这意味着图表中的连接组件是线(或循环)。您可以使用以下算法找到它们:

  • [行存在] 如果可能,选择一个阶数为 1 的数字(即,它只在列表中出现一次)。尽可能遵循对链,将它们添加到输出中并从图中删除遍历的顶点。
  • [循环存在] 如果没有次数为 1 的数字,则表示所有组件都是循环。选择任何顶点(它的度数为 2)。像以前一样遵循对,将它们添加到输出并删除遍历的顶点,但是这一次当您达到原始数量时停止。
  • 重复,直到用完图中的所有顶点。

您可以有效地运行此算法:维护一组 1 度的顶点和另一个 2 度的顶点。当您使用一条边(原始列表中的一对)时,适当地修改这些集合:从第一组,并将端点从度数 2 集合移动到度数 1 集合。或者,使用优先级队列。

您还需要对您的配对进行有效查找:构建一个从顶点到相邻顶点列表的字典。

使用这些想法,您可以在线性时间内找到最佳排序(假设 O(1) set 和 dict 实现)。

这是一个有点笨拙的实现。

import collections

def handle_vertex(v, vs):
  if v in vs[0]:
    vs[0].remove(v)
  elif v in vs[1]:
    vs[0].add(v)
    vs[1].remove(v)

def follow(edgemap, start, vs):
  """Follow a path in the graph, yielding the edges."""
  v0 = start
  last = None
  while True:
    # All vertices that we can go to next.
    next_v = [v for v in edgemap[v0] if v != last]
    if not next_v:
      # We've reached the end (we must have been a line).
      return
    assert len(next_v) == 1 or (v0 == start and len(next_v) == 2)
    next_v = next_v[0]
    # Remove the endpoints from the vertex-degree sets.
    handle_vertex(v0, vs)
    handle_vertex(next_v, vs)
    yield v0, next_v
    if next_v == start:
      # We've got back to the start (we must have been a cycle).
      return
    v0, last = next_v, v0

def pairsort(edges):
  edgemap = collections.defaultdict(list)
  original_edges = {}
  for a, b in edges:
    # Build the adjacency table.
    edgemap[a].append(b)
    edgemap[b].append(a)
    # Keep a map to remember the original order pairs appeared in
    # so we can output edges correctly no matter which way round
    # we store them.
    original_edges[a, b] = [a, b]
    original_edges[b, a] = [a, b]
  # Build sets of degree 1 and degree 2 vertices.
  vs = [set(), set()]
  for k, v in edgemap.iteritems():
    vs[len(v) - 1].add(k)
  # Find all components that are lines.
  while vs[0]:
    v0 = vs[0].pop()
    for e in follow(edgemap, v0, vs):
      yield original_edges[e]
  # Find all components that are cycles.
  while vs[1]:
    v0 = vs[1].pop()
    for e in follow(edgemap, v0, vs):
      yield original_edges[e]

input = [
    [ 4, 10],
    [ 4,  2],
    [ 0,  7],
    [ 5, 11],
    [ 6,  8],
    [ 3,  6],
    [ 9,  7],
    [ 2, 11],
    [ 9,  5],
    [ 8,  1]]

print list(pairsort(input))

【讨论】:

  • NetworkXimplements this 的库。
  • @Jochen 您仍然需要编写代码来生成给定组件的线性边列表。不过,它可能会使解决方案变得更简单。
【解决方案2】:

我不确定我是否了解您问题中的每一个细节;但如果我这样做了,那么这应该做你想要的。

基本思想很简单:对于两个一维数组,你可以循环遍历所有的pairwise 通过将它们排列在一起,保持一个不动,然后将第二个向前滚动一个增量。如果您使用 NumPy 的 roll 函数,那么当您向前滚动时从数组末尾掉落的值只会被推回前面,就像一个跑步机。

设置完成后,只需沿正确的轴区分两个向量 并对 0 求和。跟踪这些总和(tx,如下),然后获取对应的索引 这些总和的最大值,NP.argmax(tx)

import numpy as NP

# create some data:
c1 = NP.random.randint(0, 10, 15)
c2 = NP.random.randint(0, 10, 15)
c12 = NP.concatenate((c1, c2)).reshape(15, 2)

tx = []    # to hold the indices of the various orderings

for i in range(15) :
    v = NP.diff(c12, axis=0)
    num_equal_neighbors = NP.sum( NP.sum(v==0, axis=0) )
    tx.append(num_equal_neighbors)
    c2 = NP.roll(c2, 1)
    c12[:,1] = c2

现在找出两个向量的哪个排序给出了最多的“成对”匹配:

best_order = NP.argmax(tx)

因此,当排列两个一维数组时,就会出现所需的排序 使得第二个数组滚动 *best_order* 个位置 (并且第一个数组保持原样)

【讨论】:

    【解决方案3】:

    这里是Paul Hankin's answer 中描述的基本相同算法的更简单实现,使用不同的数据结构。它也以线性时间运行。

    edges = [[4, 10], [4,  2], [0,  7], [5, 11], [6,  8],
             [3,  6], [9,  7], [2, 11], [9,  5], [8,  1]]
    
    def add_edge(vertex, adj):
        edge = edges[adj[0]][:]
        ordered_edges.append(edge[:])
        edge.remove(vertex)
        new_vertex = edge[0]
        new_adj = adj_edges[new_vertex]
        new_adj.remove(adj[0])
        del adj[0]
        return new_vertex, new_adj
    
    adj_edges = {}
    for i, edge in enumerate(edges):
        for vertex in edge:
            adj_edges.setdefault(vertex, []).append(i)
    ordered_edges = []
    for vertex, adj in adj_edges.iteritems():
        while len(adj) == 1:
            vertex, adj = add_edge(vertex, adj)
    for vertex, adj in adj_edges.iteritems():
        while adj:
            vertex, adj = add_edge(vertex, adj)
    

    【讨论】:

      【解决方案4】:

      另请参阅 Longest path problem: NP 完全适用于一般图,或权重为非循环的最短路径。
      另外,请尝试显示的图表 Spanning tree: 最长比长更难。

      【讨论】:

        猜你喜欢
        • 2011-03-18
        • 1970-01-01
        • 2021-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-11
        相关资源
        最近更新 更多