合并两个列表的算法缺乏它们之间的比较

时间:2010-01-25 00:21:34

标签: algorithm language-agnostic

我正在寻找合并两个排序列表的算法, 但是他们缺少一个列表的元素和另一个列表的元素之间的比较运算符。 生成的合并列表可能不是唯一的,但是满足每个列表的相对排序顺序的任何结果都可以。 更确切地说:

假设:

  • 列出A = {a_1, ..., a_m},B = {b_1, ..., b_n}。 (它们也可以被视为集合。)
  • 在每个列表的元素中定义的优先级运算符< a_i < a_{i+1}b_j < b_{j+1} 1 <= i <= m1 <= j <= n
  • 在A和B的元素之间未定义优先级运算符: 没有为任何有效的a_i < b_ji定义j
  • 在A或B的所有元素中定义的等号运算符= (它在A的元素和B的元素之间定义。)
  • 列表A中没有两个元素相同,列表B也是如此。

农产品: 列表C = {c_1, ..., c_r},使得:

  • C = union(A, B); C的元素是A和B元素的联合。
  • 如果c_p = a_ic_q = a_ja_i < a_j,则c_p < c_q。 (元素的顺序 应保留对应于集合A和B的C的子列表。
  • 没有ijc_i = c_j。 (删除A和B之间的所有重复元素)。

我希望这个问题有道理,而且我不会问一些非常明显的问题, 或者没有解决方案的东西。

上下文

可构造数可以精确地表示在有理数域的有限多次二次扩展中(使用高度等于字段扩展数的二叉树)。 因此,可构造数字的表示必须“知道”它所代表的字段。 列表A和B表示有理数的连续二次扩展。 A和B的元素本身是可构造的数字,它们在上下文中定义 之前较小的字段(因此是优先运算符)。在添加/乘以可构造数字时, 必须首先合并二次扩展字段,以便进行二进制算术运算 可以进行操作;结果列表C是二次扩展字段 可以表示字段A和B可表示的数字。 (如果有人更好地了解如何以编程方式使用可构造数字,请告诉我。有关可构造数字的问题有arisen before,此处还有一些interesting responses关于它们的表示。)

在有人问之前,不,这个问题不属于mathoverflow;他们讨厌算法(通常是非研究生水平的数学)问题。

实际上,列表A和B是链表(实际上是以相反的顺序存储)。 我还需要跟踪C中哪些元素与A和B中哪些元素相对应,但这是一个小细节。 我寻求的算法不是mergesort中的合并操作, 因为在合并的两个列表的元素之间没有定义优先级运算符。 一切都将最终用C ++实现(我只想让运算符重载)。 这不是家庭作业,最终将是开源的,FWIW。

5 个答案:

答案 0 :(得分:3)

认为你可以做得比O(N * M)更好,尽管我很乐意做错。

既然如此,我会这样做:

  • 取A的第一个(剩余)元素。
  • 在(剩下的)中寻找它B.
    • 如果您未在B中找到它,请将其移至输出
    • 如果您确实在B中找到它,请将B中的所有内容移至并包括匹配项,然后从A中删除副本。
  • 重复上述步骤,直到A为空
  • 将B中剩余的任何内容移至输出

如果要检测A和B的不兼容顺序,请从步骤2中删除“(剩下的内容)”。搜索整个B,如果发现它“太早”则引发错误。

问题在于,给定A的一般元素,没有办法在B中以比线性时间(B的大小)更好的方式来查找它,因为我们所拥有的只是一个相等测试。但显然我们需要以某种方式找到匹配(这是我挥手的地方,我无法立即证明它)因此我们必须检查A中的每个元素以包含在B中。我们可以避免一堆比较因为这两组的顺序是一致的(至少,我认为它们是,如果不是,那就没有解决方案)。

因此,在最坏的情况下,列表的交集是空的,并且A的元素没有与B的任何元素的顺序可比。这需要建立N * M个相等测试,因此最糟糕的情况。

对于你的例子问题A =(1,2,c,4,5,f),B =(a,b,c,d,e,f),这给出了结果(1,2,a, b,c,4,5,d,e,f),这对我来说似乎不错。它在此过程中执行24次相等测试(除非我无法计算):6 + 6 + 3 + 3 + 3 + 3.与A和B合并反过来会产生(a,b,1,2,c ,d,e,4,5,f),在这种情况下具有相同数量的比较,因为匹配元素碰巧在两个列表中处于相同的索引。

从示例中可以看出,操作不能重复。合并(A,B)导致列表的顺序与合并(B,A)的顺序不一致。因此合并((合并(A,B),合并(B,A))是未定义的。通常,合并的输出是任意的,如果你使用任意订单作为新的完整订单的基础,你将生成互不兼容的订单。

答案 1 :(得分:2)

这听起来好像会使用退化形式的拓扑排序。

编辑2:

现在结合常规:

import itertools

list1 = [1, 2, 'c', 4, 5, 'f', 7]
list2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']

ibase = 0
result = []

for n1, i1 in enumerate(list1):
  for n2, i2 in enumerate(itertools.islice(list2, ibase, None, 1)):
    if i1 == i2:
      result.extend(itertools.islice(list2, ibase, ibase + n2))
      result.append(i2)
      ibase = n2 + ibase + 1
      break
  else:
    result.append(i1)
result.extend(itertools.islice(list2, ibase, None, 1))

print result

答案 2 :(得分:1)

连接两个列表是否足够?它确实保留了a和b中元素的元素的相对排序。

然后,这只是删除重复的问题。

编辑:好的,在评论讨论之后(并给出了a_i=b_i & a_j=b_j & a_i<a_j => b_i<b-J的附加条件),这是一个合理的解决方案:

  1. 确定两个列表共有的条目。对于朴素算法,这是O(n 2 ) - 你可以改进它。

  2. (可选)验证两个列表中的公共条目的顺序是否相同。

  3. 构造结果列表:a的所有元素都在第一个共享元素之前,后跟第一个共享元素之前的所有b元素,后跟第一个共享元素,依此类推。

答案 3 :(得分:1)

鉴于您表达的问题,我感觉问题可能没有解决方案。假设你有两对元素{a_1, b_1}{a_2, b_2},其中a_1 < a_2在A的排序中,而b_1 > b_2在B的排序中。现在假设{{1}根据A和B的相等运算符,和a_1 = b_1。在这种情况下,我认为您不能创建满足子列表排序要求的组合列表。

无论如何,有一种算法可以解决这个问题。 (以Java-ish编码......)

a_2 = b_2

在最坏的情况下,此算法为List<A> alist = ... List<B> blist = ... List<Object> mergedList = new SomeList<Object>(alist); int mergePos = 0; for (B b : blist) { boolean found = false; for (int i = mergePos; i < mergedList.size(); i++) { if (equals(mergedList.get(i), b)) { found = true; break; } } if (!found) { mergedList.insertBefore(b, mergePos); mergePos++; } } ,在最佳情况下为O(N**2)。 (我正在滑过一些Java实现细节......比如组合列表迭代和插入而没有重大的复杂性损失......但我认为在这种情况下可以做到。)

该算法忽略了我在第一段和其他病理中提到的病理学;例如B的元素可能“等于”A的多个元素,反之亦然。为了解决这些问题,算法需要针对mergedList中不是B实例的所有元素检查每个O(N)。这使得算法b处于最佳状态。

答案 4 :(得分:0)

如果元素是可以清除的,可以在O(N)时间内完成,其中N是A和B中元素的总数。

def merge(A, B):
    # Walk A and build a hash table mapping its values to indices.
    amap = {}
    for i, a in enumerate(A):
        amap[a] = i

    # Now walk B building C.
    C = []
    ai = 0
    bi = 0
    for i, b in enumerate(B):
        if b in amap:
            # b is in both lists.
            new_ai = amap[b]
            assert new_ai >= ai  # check for consistent input
            C += A[ai:new_ai]    # add non-shared elements from A
            C += B[bi:i]         # add non-shared elements from B
            C.append(b)          # add the shared element b
            ai = new_ai + 1
            bi = i + 1
    C += A[ai:]  # add remaining non-shared elements from A
    C += B[bi:]  # from B
    return C

A = [1, 2, 'c', 4, 5, 'f', 7]
B = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print merge(A, B)

(这只是Anon算法的一个实现。请注意,您可以在不损害性能的情况下检查不一致的输入列表,并且随机访问列表是必要的。)