我有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.
我也会用你的想法建立较小的直方图并调整更大的直方图。
听起来不错?
答案 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
。
如果我知道集合的总大小,可能会有更好的解决方案。但是,没有任何解决方案可以比 O(T)更快,而且这只是对数慢的。
可能还有一个改进:如果您在直方图中不仅存储元素和频率,还存储包含元素的集合(每个元素只需另一个std::set
),您将能够有效地选择所有包含最常见元素的集合。