在因子分解期间选择最常见对象的算法

时间:2017-11-23 16:50:11

标签: algorithm graph

我有N个对象,还有M组这些对象。集合是非空的,不同的,并且可以相交。通常,M和N具有相同的数量级,通常M> 1。 Ñ

历史上我的集合按原样编码,每个集合只包含其对象的表(数组),但我想创建一个更优化的编码。通常一些对象存在于大多数集合中,我想利用它。

我的想法是将集合表示为堆栈(即单向链表),而它们的底部部分可以在不同的集合中共享。它也可以定义为树,而每个节点/叶子都有一个指向其父节点的指针,但不是子节点。 这样的数据结构将允许使用最常见的对象子集作为根,所有适当的集合可以继承"继承"。

最有效的编码由以下算法计算。我将它写成递归伪代码。

BuildAllChains()
{
    BuildSubChains(allSets, NULL);
}

BuildSubChains(sets, pParent)
{
    if (sets is empty)
        return;

    trgObj = the most frequent object from sets;

    pNode = new Node;
    pNode->Object = trgObj;
    pNode->pParent = pParent;

    newSets = empty;
    for (each set in sets that contains the trgObj)
    {
        remove trgObj from set;
        remove set from sets;

        if (set is empty)
            set->pHead = pNode;
        else
            newSets.Insert(set);            
    }

    BuildSubChains(sets, pParent);
    BuildSubChains(newSets, pNode);
}

注意:伪代码是以递归方式编写的,但不应该使用技术上天真的递归,因为在每个点上分裂都不平衡,并且在简并的情况下(很可能,因为源数据不是#39; t random)递归深度为O(N)。 实际上我使用循环+递归的组合,而递归总是在较小的部分上调用。

所以,我们的想法是每次选择最常见的对象,创建一个"子集"它继承了它的父子集,包含它的所有集合,以及到目前为止选择的所有前驱 - 都应该基于这个子集。

现在,我试图找出一种从集合中选择最频繁对象的有效方法。最初我的想法是计算所有对象的直方图,并对其进行一次排序。然后,在递归期间,每当我们删除一个对象并仅选择包含/不包含它的集合时 - 推导出剩余集合的排序直方图。但后来我意识到这不是微不足道的,因为我们删除了许多集合,每个集合包含许多对象。

当然我们可以直接选择每次最频繁的对象,即O(N * M)。但它看起来也很差,在一个简并的情况下,一个物体存在于几乎所有或几乎没有的集合中,我们可能需要重复这个O(N)次。 OTOH对于那些特定情况就地调整分类直方图可能是首选的方法。

到目前为止,我还没有找到足够好的解决方案。任何想法,将不胜感激。提前谢谢。

更新

@Ivan:首先感谢答案和详细分析。 我执行存储直方图中的元素列表而不是仅计数。实际上我使用非常复杂的数据结构(与STL无关)与侵入式容器,corss链接指针等等。我从一开始就计划这个,因为在我看来删除元素后的直方图调整是微不足道的。

我认为你的建议的主要观点,我自己没有弄清楚,在每一步,直方图应该只包含仍然存在于家庭中的元素,即它们不得包含零。我认为在分裂非常不均匀的情况下,为较小的部分创建新的直方图太昂贵了。但是将其限制为仅存在的元素是一个非常好的主意。

因此,我们删除较小的家庭,调整"大"直方图和建立"小"一。现在,我需要澄清如何对大直方图进行排序。

我首先想到的一个想法是在每次删除单个元素后立即修复直方图。即对于我们删除的每个集合,对于集合中的每个对象,将其从直方图中删除,如果排序被破坏 - 将直方图元素与其邻居交换,直到恢复排序。

如果我们删除少量的物体,我们不需要遍历整个直方图,我们做了一个微泡"这似乎很好。分类。 但是,当删除大量对象时,最好只删除所有对象,然后通过快速排序重新排序数组。

那么,你对此有更好的了解吗?

UPDATE2:

我考虑以下内容:直方图应该是一个数据结构,它是一个二叉搜索树(当然是自动平衡的),而树的每个元素都包含适当的对象ID和它所属的集合列表(至今)。比较标准是此列表的大小。

每个集合应该包含它现在包含的对象列表,而"对象"有直接指向元素直方图的指针。此外,每个集合应包含到目前为止匹配的对象数,在开始时设置为0。

从技术上讲,我们需要一个交叉链接列表节点,即同时存在于2个链接列表中的结构:在直方图元素的列表中,以及在该列表中。此节点还应包含指向直方图项和集的指针。我将其称为“交叉链接”#34;。

挑选最常见的对象只是在树中找到最大值。 调整这样的直方图是O(M log(N)),而M是当前受影响的元素的数量,如果只有一小部分受影响,则小于N.

我也会用你的想法建立较小的直方图并调整更大的直方图。

听起来不错?

1 个答案:

答案 0 :(得分:1)

我用T表示集合的总大小。我提出的解决方案在时间上工作 O(T log T log N)

为清楚起见,我用 set 表示初始集,用 family 表示这些集的集合。

确实,让我们存储一个直方图。在BuildSubChains函数中,我们维护了当前在集合中呈现的所有元素的直方图,按频率排序。它可能类似std::set(frequency, value),可能有交叉引用,因此您可以按值找到元素。现在采用最常见的元素很简单:它是直方图中的第一个元素。但是,保持它是比较棘手的。

你将你的家族分成两个子系列,一个包含最常见的元素,一个不包含。让总大小为T'和T''。采用总体尺寸最小的族,从直方图中删除其集合中的所有元素,使新的直方图在运行中。现在你有两个系列的直方图,并且它是按时建立的 O(min(T',T'')log n),其中 log n 来自操作与std::set

乍一看似乎它在二次时间内起作用。但是,它更快。看看任何一个元素。每次我们从直方图中明确地删除此元素时,其族的大小至少减半,因此每个元素将直接参与不超过 log T 删除。因此总共会有 O(T log T)操作,直方图。

如果我知道集合的总大小,可能会有更好的解决方案。但是,没有任何解决方案可以比 O(T)更快,而且这只是对数慢的。

可能还有一个改进:如果您在直方图中不仅存储元素和频率,还存储包含元素的集合(每个元素只需另一个std::set),您将能够有效地选择所有包含最常见元素的集合。