【问题标题】:Algorithms for compression of set tries用于压缩集合尝试的算法
【发布时间】:2012-03-13 09:04:04
【问题描述】:

我有一组集合,我想将它们放在trie 中。

普通的尝试是由元素的字符串组成的——也就是说,元素的顺序很重要。集合缺乏明确的顺序,因此有可能进行更大的压缩。

例如,给定字符串 "abc""bc""c",我将创建 trie:

(*,3) -> ('a',1) -> ('b',1) -> ('c',1)
      -> ('b',1) -> ('c',1)
      -> ('c',1)

但考虑到集合 { 'a', 'b', 'c' }{ 'b', 'c' }{ 'c' },我可以创建上述特里树或这十一个中的任何一个:

(*,3) -> ('a',1) -> ('b',1) -> ('c',1)
      -> ('c',2) -> ('a',1)

(*,3) -> ('a',1) -> ('c',1) -> ('b',1)
      -> ('b',1) -> ('c',1)
      -> ('c',1)

(*,3) -> ('a',1) -> ('c',1) -> ('b',1)
      -> ('c',2) -> ('a',1)

(*,3) -> ('b',2) -> ('a',1) -> ('c',1)
                 -> ('c',1)
      -> ('c',1)

(*,3) -> ('b',1) -> ('a',1) -> ('c',1)
      -> ('c',2) -> ('b',1)

(*,3) -> ('b',2) -> ('c',2) -> ('a',1)
      -> ('c',1)

(*,3) -> ('b',1) -> ('c',1) -> ('a',1)
      -> ('c',2) -> ('b',1)

(*,3) -> ('c',2) -> ('a',1) -> ('b',1)
      -> ('b',1) -> ('c',1)

(*,3) -> ('c',2) -> ('a',1) -> ('b',1)
                 -> ('b',1)

(*,3) -> ('c',2) -> ('b',1) -> ('a',1)
      -> ('b',1) -> ('c',1)

(*,3) -> ('c',3) -> ('b',2) -> ('a',1)

因此显然有压缩空间(7 个节点到 4 个节点)。

怀疑根据子节点的相对频率在每个节点上定义一个本地顺序会做到这一点,但我不确定,而且可能过于昂贵。

所以在我敲白板并开始研究我自己的压缩算法之前,有没有现成的压缩算法?它有多贵?它是一个批量过程,还是可以按插入/删除完成?

【问题讨论】:

  • 我认为 trie 不是一个很好的表示集合的结构。位数组的集合不是更好吗?您希望进行哪些操作?为什么这么担心内存?
  • @svick:也许吧,但是我的集合是从大量元素中提取的,所以位数组可能不是很有效。遍历(子集,频率)对。因为我有很多数据。
  • 你打算做什么操作?传统的 trie 可以有效地告诉您给定字符串是否包含在它所代表的字符串集中。如果您的 trie 重新排序其字符串以最小化结构大小,您如何实际测试给定的一组字符是否包含在 trie 中?似乎您需要搜索每个排列。
  • @Weeble:您可以将每个节点的本地订单与每个节点的子节点一起存储。当查看一个节点时,遍历其子节点,并遍历到第一个与您包含的元素匹配的节点。
  • 您是在寻找只是压缩以便稍后解压,还是您还想对压缩结构执行集合操作?

标签: algorithm language-agnostic compression trie


【解决方案1】:

基本上你应该构建一个依赖图。如果元素 y 仅在 x 出现时出现,则从 x 到 y 绘制一条边(在相等的情况下,只需按字典顺序排列)。结果图是一个 DAG。现在,对该图进行拓扑排序,以获得元素的顺序。只要您可以选择两个(或更多元素)之一,请选择出现次数较多的一个。

【讨论】:

    【解决方案2】:

    我认为您应该根据项目频率对集合进行排序,并且正如您所怀疑的那样,这会得到很好的启发。在FP-growth(频繁模式挖掘)中使用相同的方法以紧凑的方式表示项目集。

    【讨论】:

    • 整圈!我实际上在看这个,因为我认为 FP-growth 中使用的全局顺序是不够的。
    • 您可以重建子树,根据该子树中的项目频率,它可以为您提供更好的压缩,但在这种情况下我们需要执行更多的计算。
    【解决方案3】:

    我的怀疑是最大压缩会将最常见的元素保留在顶部(如上一个示例中所示)。

    压缩算法将从整个集合集合和顶部节点开始,并递归地为每个包含最常见元素的子集创建节点

    Compress(collection, node):
        while NOT collection.isEmpty? 
          e = collection.find_most_common_element
          c2 = collection.find_all_containing(e)
          collection = collection - c2
          if e==NIL //empty sets only
             node[END_OF_SET]=node
          else
            c2.each{set.remove(e)}
            node[e]=new Node
            Compress(c2,node[e])
          end
        end
    

    生成的树将有一个特殊的 End-of-set 标记来表示一个完整的集合在该节点处结束。对于您的示例,它将是

     *->(C,3)->(B,2)->(A,1)->EOS
                    ->EOS
              ->EOS
    

    删除一个集合很容易,只需删除它的 EOS 标记(以及任何变为空的父节点)。您可以动态插入 - 在每个节点处,下降到具有最多子节点的匹配元素,直到没有匹配项,然后使用上面的算法 - 但保持最大压缩会很棘手。当元素 B 获得的子元素多于元素 A 时,您必须将包含 A 和 B 的所有集合移动到 B 节点中,这将涉及对 A 的所有子元素的完整搜索。但是,如果您不对其进行压缩,则包含搜索不再与设置的大小呈线性关系。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多