在不同的数组中查找“最常见元素”的算法

5
例如,我有5个数组,其中插入了一些元素(数字):
1,4,8,10 1,2,3,4,11,15 2,4,20,21 2,30
我需要在这些数组中找到最常见的元素,并且每个元素都应该一直到最后(请参见下面的示例)。在此示例中,粗体组合(或具有“30”结尾的相同组合,它是“相同的”)将是最佳选择,因为它包含不同元素数量最少的元素(仅为2个,4和2/30)。
如果我有“4”,则不能使用以下组合(请参见下面的示例)。因此,组合必须一直到最后。
1,4,8,10 1,2,3,4,11,15 2,4,20,21 2,30
编辑2:或者
1,4,8,10 1,2,3,4,11,15 2,4,20,21 2,30
任何其他内容都不好。
是否有某种算法可以加速此过程(如果我有数千个数组,每个数组都有数百个元素)?
明确一下 - 解决方案必须包含最少数量的不同元素,并且组(相同的数字)必须从第一个 - 较大的开始到最后 - 最小的。因此,在上面的示例中,4,4,4,2比4,2,2,2更好,因为在第一个示例中,4的组较大。
编辑:更具体地说。解决方案必须包含最少数量的不同元素,并且这些元素必须从第一个到最后一个分组。因此,如果我有三个数组,如下所示:
1,2,3 1,4,5 4,5,6
解决方案是1,1,4或1,1,5或1,1,6,而不是2,5,5,因为1的组(两个)比2��组(仅一个)要大。
谢谢。

编辑4:@spintheblack 1,1,1,2,4是正确的解决方案,因为第一次使用的数字(假设在位置1)不能以后再使用(除非它在相同的1组中)。我会说分组具有“优先级”吗?另外,我没有提到它(对此感到抱歉),但数组中的数字没有以任何方式排序,我在这篇文章中以这种方式输入它,因为这样更容易让我跟进。


1
我不理解“一直走到底”的部分。为什么在你的第二个例子的第三行中没有突出显示4,而在同一个例子的第二行中也没有突出显示2? - Lasse V. Karlsen
1
让我试着解释一下你修改后的算法。你想要选择组合,以便从一个数字到下一个数字的变化最少,其中“组合”是从每个集合中选择一个数字,并且你需要一个贪心算法,它首先选择最长的连续数字。贪心部分是解释为什么“1,1,X”比“2,5,5”更好的原因。如果贪心部分是错误的,那么我仍然不明白为什么“1,1,6”比“2,5,5”更好(两者都有2个数字,其中一个数字出现了2次,另一个数字出现了1次)。 - Lasse V. Karlsen
我需要它那样 - 是的,每个数组只有一个数字,并且它们必须以这种方式排列,始终选择的数字集的第一组是最长的(就像您解释的那样)。 - svenkapudija
1
@Lasse 我写的这个组合是不好的。 - svenkapudija
1
你为什么抱怨? 如果问题没有解释清楚,它不太可能是作业 :-) - Déjà vu
显示剩余26条评论
5个回答

3

以下是您需要采取的方法,如果arrays是包含每个单独数组的数组。

  1. i = 0开始
  2. current = arrays[i]
  3. 循环从i+1len(arrays)-1
  4. new = current & arrays[i] (找到相同元素的集合交集)
  5. 如果new中有任何元素,请执行步骤6,否则跳至7
  6. current = new,返回第3步(继续循环)
  7. 打印或从当前元素中生成一个元素,current = arrays[i],返回第3步(继续循环)

这是Python实现:

def mce(arrays):
  count = 1
  current = set(arrays[0])
  for i in range(1, len(arrays)):
    new = current & set(arrays[i])
    if new:
      count += 1
      current = new
    else:
      print " ".join([str(current.pop())] * count),
      count = 1
      current = set(arrays[i])
  print " ".join([str(current.pop())] * count)

>>> mce([[1, 4, 8, 10], [1, 2, 3, 4, 11, 15], [2, 4, 20, 21], [2, 30]])
4 4 4 2

mce([[1,4],[1,2],[1,2],[2],[3,4]]) 1 1 1 2 3
- dfb
@spintheblack 你希望它是什么? - Andrew Clark
请看下面我的解决方案 - 我还不确定我们是否在讨论同一个问题 :) - dfb
你的看法可能是正确的,但在我看来,OP似乎试图将转换的数量最小化(4->2->4与1->2->3相同),而不一定是不同元素的数量。我可能对此有所误解,希望OP能够澄清 :) - Andrew Clark
每个交集操作都不是O(n)吗?这意味着,解决方案不是O(nm)吗? - CMR

2
这现在已经成为一个带有变化的图形问题。
问题是一张有向无环图,它连接了不同站点之间的关系,目标是在乘坐火车/电车时最小化换乘次数。
例如,这个集合列表:
1,4,8,10 <-- 站点 A 1,2,3,4,11,15 <-- 站点 B 2,4,20,21 <-- 站点 C 2,30 <-- 站点 D,目的地
他需要选择他到达和离开的站点上有的线路,例如,他不能从站点A选择10号线,因为10号线不到达站点B。
所以,这是可用线路及其停靠站点的集合:
A B C D line 1 -----X-----X----------------- line 2 -----------X-----X-----X----- line 3 -----------X----------------- line 4 -----X-----X-----X----------- line 8 -----X----------------------- line 10 -----X----------------------- line 11 -----------X----------------- line 15 -----------X----------------- line 20 -----------------X----------- line 21 -----------------X----------- line 30 -----------------------X-----
如果我们认为考虑中的线路必须在至少两个连续站之间运行,那么我将用等号突出显示可选线路:
A B C D line 1 -----X=====X----------------- line 2 -----------X=====X=====X----- line 3 -----------X----------------- line 4 -----X=====X=====X----------- line 8 -----X----------------------- line 10 -----X----------------------- line 11 -----------X----------------- line 15 -----------X----------------- line 20 -----------------X----------- line 21 -----------------X----------- line 30 -----------------------X-----
然后他需要选择一种从A到D的方式,使换乘次数最少。
由于他解释了他希望首先乘坐长途车,因此以下顺序似乎是最佳解决方案:
  • 乘坐4号线从A站到C站,然后换乘2号线从C到D
代码示例:
stops = [
    [1, 4, 8, 10],
    [1,2,3,4,11,15],
    [2,4,20,21],
    [2,30],
]

def calculate_possible_exit_lines(stops):
    """
    only return lines that are available at both exit
    and arrival stops, discard the rest.
    """

    result = []
    for index in range(0, len(stops) - 1):
        lines = []
        for value in stops[index]:
            if value in stops[index + 1]:
                lines.append(value)
        result.append(lines)
    return result

def all_combinations(lines):
    """
    produce all combinations which travel from one end
    of the journey to the other, across available lines.
    """

    if not lines:
        yield []
    else:
        for line in lines[0]:
            for rest_combination in all_combinations(lines[1:]):
                yield [line] + rest_combination

def reduce(combination):
    """
    reduce a combination by returning the number of
    times each value appear consecutively, ie.
    [1,1,4,4,3] would return [2,2,1] since
    the 1's appear twice, the 4's appear twice, and
    the 3 only appear once.
    """

    result = []
    while combination:
        count = 1
        value = combination[0]
        combination = combination[1:]
        while combination and combination[0] == value:
            combination = combination[1:]
            count += 1
        result.append(count)
    return tuple(result)

def calculate_best_choice(lines):
    """
    find the best choice by reducing each available
    combination down to the number of stops you can
    sit on a single line before having to switch,
    and then picking the one that has the most stops
    first, and then so on.
    """

    available = []
    for combination in all_combinations(lines):
        count_stops = reduce(combination)
        available.append((count_stops, combination))
    available = [k for k in reversed(sorted(available))]
    return available[0][1]

possible_lines = calculate_possible_exit_lines(stops)
print("possible lines: %s" % (str(possible_lines), ))
best_choice = calculate_best_choice(possible_lines)
print("best choice: %s" % (str(best_choice), ))

这段代码输出:

可能的路线:[[1, 4], [2, 4], [2]]
最佳选择:[4, 4, 2]

正如我所说,我列出了在站点之间的路线,并且上面的解决方案可以作为你需要从每个站点下车的路线或者你需要到达下一个站点的路线。

所以路线是:

  • 在A站点搭乘4号线并乘坐到B站点,然后到C站点
  • 在C站点搭乘2号线并乘坐到D站点

这里可能有一些边缘情况,上述代码可能无法处理。

然而,我不再为此问题烦恼。提问者完全无法清晰、简洁地表达他的问题,我担心对上述文本和/或代码进行任何更正以适应最新评论都只会引起更多的评论,导致问题的又一个版本等等。提问者已经竭尽全力避免回答直接的问题或解释问题。


正确 :) 除了列表有点不同,没有什么特别之处,但它具有1的“偏移量”,因此1、4、8、10将是线路到A(而不是从A到B),1、2、3、4、11、15线路到B,2、4、20、21线路到C和2、30线路到D。因此,第一组自动“退出”,因为我不关心哪条线路驱动器停在A,我只对其他三个感兴趣。 - svenkapudija
我将在已经做出的假设基础上完成这个答案,然后真的会停下来。如果我提供的代码答案不够好,那就这样吧。 - Lasse V. Karlsen
在他的例子中,最后一组是无意义的,因为(在他的例子中)它将D连接到其他不重要的东西,因为D是我们的目的地。在我的情况下,第一组是无意义的,因为我不关心哪些线路驱动到A,我只对其他三个感兴趣。所以在他的例子中 - 您可以删除最后一组,在我的情况下,您可以删除第一组。 - svenkapudija
我已经确定了我的答案,我不在乎你现在的评论,因为你已经表现出不愿意提供清晰简明的答案。我不知道你为什么这样做,但证据(你的问题上有26个以上的评论,几乎所有人都要求澄清,并且除了我的三个答案之外,其他所有答案都有评论说“我可能误解了这个问题,但是…”)。作为今后的参考,“沟通、沟通、沟通”。 - Lasse V. Karlsen
还要注意,我的解决方案是纯暴力的,@Andrew(https://dev59.com/V1TTa4cB1Zd3GeqPsnc3#5046885)提供的答案,如果对您所有的情况都正确,那么它绝对是一个更好的解决方案。 - Lasse V. Karlsen
显示剩余2条评论

2

如果所有的内容都是数字列表,并且已经排序好了,那么:

  1. 将它们转换成位图数组。
  2. 持续进行“与”运算,直到结果为零。上一个值中1所在的位置表示第一个元素。
  3. 从下一个元素重新开始步骤2。

如果前一个值包含多个1(1,4,51,4,62,5,7),则迭代的某些元素可能会产生解决方案 [1,1,2][1,1,5][1,1,7],或者用4替换1后得到相同的3个解决方案。 - Lasse V. Karlsen
@Lasse,与此同时,问题又改变了。 - CMR
我认为贪心算法并不可行 - [1.4]。[1.2]。[1.2]。[2]。[3,4]。这个算法不会返回[2,4],而是[1,3,4],你觉得呢? - dfb
也许我还是误解了问题-这个问题的正确答案是什么?重新表述我的答案,应该是[4,2,2,2,4]而不是[1,1,1,2,4]。 - dfb

1

我假设“不同的元素”实际上并不需要是不同的,它们可以在最终解决方案中重复。也就是说,如果给出[1],[2],[1],那么明显的答案[1, 2, 1]是允许的。但我们会将其视为具有3个不同的元素。

如果是这样,那么这里是一个Python解决方案:

def find_best_run (first_array, *argv):
    # initialize data structures.
    this_array_best_run = {}
    for x in first_array:
        this_array_best_run[x] = (1, (1,), (x,))

    for this_array in argv:
        # find the best runs ending at each value in this_array
        last_array_best_run = this_array_best_run
        this_array_best_run = {}

        for x in this_array:
            for (y, pattern) in last_array_best_run.iteritems():
                (distinct_count, lengths, elements) = pattern
                if x == y:
                    lengths = tuple(lengths[:-1] + (lengths[-1] + 1,))
                else :
                    distinct_count += 1
                    lengths = tuple(lengths + (1,))
                    elements = tuple(elements + (x,))

                if x not in this_array_best_run:
                    this_array_best_run[x] = (distinct_count, lengths, elements)
                else:
                    (prev_count, prev_lengths, prev_elements) = this_array_best_run[x]
                    if distinct_count < prev_count or prev_lengths < lengths:
                        this_array_best_run[x] = (distinct_count, lengths, elements)

    # find the best overall run
    best_count = len(argv) + 10 # Needs to be bigger than any possible answer.
    for (distinct_count, lengths, elements) in this_array_best_run.itervalues():
        if distinct_count < best_count:
            best_count = distinct_count
            best_lengths = lengths
            best_elements = elements
        elif distinct_count == best_count and best_lengths < lengths:
            best_count = distinct_count
            best_lengths = lengths
            best_elements = elements

    # convert it into a more normal representation.                
    answer = []
    for (length, element) in zip(best_lengths, elements):
        answer.extend([element] * length)

    return answer

# example
print find_best_run(
    [1,4,8,10],
    [1,2,3,4,11,15],
    [2,4,20,21],
    [2,30]) # prints [4, 4, 4, 30]

这里有一个解释。 ...this_run 字典的键是当前数组中的元素,它们的值是元组 (distinct_count, lengths, elements)。我们试图最小化 distinct_count,然后最大化长度(长度是一个元组,因此这将优先考虑第一个位置具有最大值的元素),并跟踪元素以进行结尾。在每个步骤中,我构造了所有可能的运行,这些运行是由前一个数组与此元素按顺序组合而成的,并找出哪些运行对当前运行最佳。当我到达结尾时,我选择最佳的整体运行,然后将其转换为常规表示并返回。
如果您有长度为 M 的 N 个数组,则应该需要 O(N*M*M) 的时间来运行。

这看起来不错 :) 我需要一些时间将其重写为php/java(因为我不熟悉Python),所以如果你有时间,你可以将其重写为php/java - 非常感谢,如果没有 - 我会将其与你的Python实现一起发布在第一篇帖子中。无论如何,干得好 ;) - svenkapudija
@svebee:抱歉,我不太擅长PHP或Java,所以我不会重写它。但是你可以使用HashMaps来处理*array_best_run数据结构。Java没有提供轻松比较数组的能力,因此长度的比较需要更多的代码。如果我必须重写(或维护)它,我会消除distinct_count,因为lengths已经知道它的长度。 - btilly

1

根据评论,我会尝试解释一下。我们有N个数组,想要找到从每个数组中选出一个值后,所有数组中最常见的值。有几个限制条件:1)我们希望使用最少数量的不同值;2)最常见的是相似字母的最大分组(为了更清晰,与上文不同)。因此,4个t和1个p胜过3个x和2个y。

我认为这两个问题都不能贪心地解决——这里有一个反例[[1,4],[1,2],[1,2],[2],[3,4]]——贪心算法会选择[1,1,1,2,4](3个不同数字)[4,2,2,2,4](两个不同数字)

这看起来像是一个二分图匹配问题,但我仍在构思中。

编辑:忽略;这是一个不同的问题,但如果有人能解决它,我会非常感兴趣。

编辑2:对于任何感兴趣的人,我误解的问题可以被表述为击中集问题的一个实例,参见http://en.wikipedia.org/wiki/Vertex_cover#Hitting_set_and_set_cover。基本上,二分图的左侧将是数组,右侧将是数字,边将在包含每个数字的数组之间绘制。不幸的是,这是NP完全问题,但上面描述的贪心解决方案基本上是最佳近似解。


答案不是 [1, 2, 3] 或者 [1, 2, 4] 吗? - CMR
请看我在你的回答中的评论 :). 我正在尝试最小化不同元素的数量,同时最大化集合大小,请参见2)。 - dfb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接