【问题标题】:How can I efficiently find subsets of a set in a map?如何有效地在地图中找到集合的子集?
【发布时间】:2011-12-14 12:42:37
【问题描述】:

考虑到我有一组值到值的映射,在 Java 中,这个映射的类型是:

Map<Set<Object>, Object> setToObjMap;

给定一组新对象set,我希望在 setToObjMap 中找到关联键是“搜索集”的子集的所有值。

例如,如果我的地图是:

["telephone", "hat"] -> "book"
["laugh", "fry", "mouse"] -> "house"
["dog", "cat"] -> "monster"

然后,给定搜索集["telephone", "hat", "book", "dog", "cat"],我将检索值“book”和“monster”。

实际上,setToObjectMap 中可能有数万个条目,集合中有数万个可能的值。搜索集通常包含大约 10 个元素。

我希望有一种有效的方法可以做到这一点,不需要遍历地图中的所有键。谁能给点建议?

【问题讨论】:

  • 除非您自己实现了MapSet 并因此可以访问它们的内部,否则这个问题没有实际意义。任何合理的库存实现都不会提供允许解决方案的公共接口,而不是显而易见的。
  • 是的,显然我在问我如何实现数据结构,因此问题上的“数据结构”标签。

标签: algorithm data-structures


【解决方案1】:

你可以创建一个查找数据结构

Map<String,List<Finder>>

Finder 有一个 int countmax,以及一个 res 字。请注意,该列表用于处理setToObjMap 中的许多集合可以共享同一个单词的情况,这在您的示例中没有。

"telephone" -> [{res:"book",count=0,max=2}]
"hat" -> same object as above
"laugh" -> [{res:"house",count=0,max=3}]
...

此查找集合构建速度快,查找后刷新速度更快。

查找算法遍历set,对于每个单词,以及该单词的每个Finder,它都会增加count 变量。第二遍,取查找图的所有值,如果count==max,则将res放入结果中。

初始化算法:

for Entry e in setToObjMap
  Finder f = new Finder(e.value, 0, e.key.size) // res, count, max
  for String word in e.key
    lookup.get(word).add(f)

查找算法:

for String word in set
  for Finder f in lookup.get(word)
    f.count ++
for Finder f in lookup.values()
  if (f.count==f.max)
    res.add(f.res)

重置算法:

for Finder f in lookup.values()
    f.count = 0

关于复杂度,如果n是set中的元素个数,m是setToObjMap中值的个数,复杂度是O(n+m)

【讨论】:

  • 这看起来很有趣,但是在盯着它看了几分钟后,我无法理解它——你能把它充实一点吗?例如,“查找计数”到底是什么?该算法的哪一部分是预先计算的?你知道它的时间复杂度是多少吗?
  • 刚刚用算法伪代码和复杂性编辑了我的答案。
  • 嗯,但是考虑到 O(m) 复杂度,不是天真的解决方案,即遍历地图,比这更好吗?
  • O(m) 用于遍历 Map,是的,但是对于每个 Map 项目,您需要检查该集合是否是 set 的子集,这是一个 O(n) 复杂度其中 n 是集合的平均大小。总数是 O(n*m),这要差得多。
【解决方案2】:

如果所讨论的集合很小,而映射很大,最好的方法是生成集合的所有子集并在映射中查找。

如果您的集合具有 k 元素并且映射中有 n 关联,则需要 2^k 查找与 n 子集检查相反。您会看到,对于 n = 1000k = 20,这是一个坏主意,但对于 n = 100000k = 10,这将是一个胜利。

【讨论】:

  • 是的,这是一个有趣的建议(尽管我认为@larsmans 击败了你)。我想我会坚持其他建议。
  • 很公平,提交时间并不是唯一的标准。
【解决方案3】:

另一种选择是建立从单个元素到键集的索引:

"hat" -> ["telephone", "hat"]
"telephone" -> ["telephone", "hat"]
"laugh"->["laugh", "fry", "mouse"]
"fry"->["laugh", "fry", "mouse"]
"mouse"->["laugh", "fry", "mouse"]
"dog" -> ["dog", "cat"]
"cat" -> ["dog", "cat"]

它将允许通过输入快速查询键集。

【讨论】:

    【解决方案4】:

    遍历地图是一种选择。这需要 O(n × m) 时间,其中 n 是地图中的条目数,m是查询集中的项目数; m 因素是由于子集检查而产生的。

    另一个选项是生成集合的all subsets 以在地图中搜索和查找这些集合。这需要 O(2^m) 时间。如果 2^mn 相比较小(因此 m 应该非常小),这可能比第一个选项更快。在您的示例用例中,2^m = 2^10 = 1024,小于数万。

    如果已知查询集大小不同,您甚至可以使用混合策略:计算数字 2^m 并检查它是否小于 n,然后根据检查结果选择这两个选项中最好的。

    【讨论】:

    • 根据您的第一个建议,我希望有比遍历整个地图的“明显”解决方案更好的方法。你的第二个建议很有趣,但我会坚持看看是否有比 O(2^m) 更好的方法。
    • 第一个解的子集检查不明显,复杂度是O(n),n是子集的平均大小,所以第一个解的复杂度是O(n*m)。
    • @solendil:是的,我会把它添加到答案中。
    【解决方案5】:

    如果您的集合的成员服从某种排序,那么您可以将它们保存在树结构中,并在叶子处附加键值映射。然后,当您沿着子集的路径沿着树向下移动时,该子树下的所有叶子都将是包含您的子集的集合。

    【讨论】:

      猜你喜欢
      • 2017-11-02
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 2014-07-28
      相关资源
      最近更新 更多