处理按位和添加项目的有效方法

时间:2009-06-02 02:46:28

标签: algorithm language-agnostic data-structures

所以,假设你有一个项目集合。每个项目都有一个标识符,可以使用位域表示。举个简单的例子,假设你的收藏是: 0110,0111,1001,11011,1110,1111

因此,您希望实现一个函数Remove(bool bitval, int position)。例如,对Remove(0, 2)的调用将删除索引2(即第3位)为0的所有项。在这种情况下,仅为1001。 Remove(1,1)将删除1110,1111,0111和0110.在可能的情况下提出O(n)集合是非常简单的(只需使用链表),其中n是项目中的项目数。采集。通常,要移除的项目的数量将是O(n)(假设给定位具有≥c%的概率为1并且≥c%的概率为0,其中c是某个常数> 0) ,所以“更好”的算法,其中某种方式是O(l),其中l是被删除的项目的数量,是令人兴奋的。

是否可以定义一个数据结构,其中平均(或更好,最坏情况)的移除时间优于O(n)?二叉树可以做得很好(只需删除高度为m的所有左/右分支,其中m是被测试的索引),但我想知道是否有任何方法可以做得更好(而且老实说,我是不确定如何以有效的方式去除特定高度的所有左或右分支)。或者,是否有证据表明不可能做得更好?

编辑:我不确定我在效率方面的确切期望(对不起Arno),但是对它的可能应用的基本解释是:假设我们正在使用二元决策树。这样的树可以用于游戏树或拼图解算器或其他任何东西。进一步假设树足够小,我们可以将所有叶节点都放入内存中。每个这样的节点基本上只是列出所有决策的位域。现在,如果我们想从这棵树中删除任意决策,一种方法就是跳到特定决策的高度并修剪每个节点的左侧或右侧(左侧表示一个决策,右侧表示另一个) 。通常在决策树中,您只想一次修剪子树(因为该子树的父节点与其他子树的父节点不同,因此应该在一个子树中修剪的决定不应该从其他子树中删除),但是某些类型的情况可能并非如此。此外,您通常只想修剪特定节点下的所有内容,但在这种情况下,您将在节点下面留下一些内容,但也会在树中的其他节点下面进行修剪。

无论如何,这是一个基于好奇心的问题;我不确定使用任何结果是否切合实际,但我对人们所说的内容感兴趣。

编辑: 进一步思考它,我认为树方法实际上是O(n / logn),假设它是相当密集的。证明: 假设您有一个包含n个项目的二叉树。它的高度是log(n)。移除一半底部将需要n / 2次移除。删除上面一半的行将需要n / 4。每行的操作总和为n-1。因此,平均删除次数为n-1 / log(n)。

4 个答案:

答案 0 :(得分:1)

如果您的位域长度有限,则以下内容可能有效:

  • 首先,将集合中的位域表示为布尔数组,因此在您的情况下(4位位域),new bool[16];
  • 将此布尔数组转换为位域本身,因此在这种情况下为16位位域,其中每个位表示是否包含与其索引对应的位域

然后操作变为:

  • Remove(0, 0) =并使用位掩码1010101010101010
  • Remove(1, 0) =并使用位掩码0101010101010101
  • Remove(0, 2) =并使用位掩码1111000011110000

请注意,更复杂的“添加/删除”操作也可以添加为O(1)位逻辑。

唯一的缺点是需要额外的工作来将生成的16位位域解释为一组值,但是查找数组也可能不会太糟糕。

<强>附录:
其他下方:

  • 一旦超过整数的大小,原始位字段的每个添加位都将使存储空间加倍。但是,这并不比使用另一个集合的典型场景差,在这个场景中,您必须平均存储一半可能的位掩码值(假设典型场景不存储更少的剩余值)。

  • 一旦超过整数的大小,每增加一位也会使实现逻辑所需的'和'操作数加倍。

所以基本上,我会说如果你的原始位域不比一个字节大很多,那么使用这种编码可能会更好,除此之外你可能会更好地使用原始策略。

进一步补充:
如果你只执行Remove次操作,随着时间的推移逐渐减少设置的状态空间,你可以通过做一个更聪明的抽象来进一步扩展这种方法(没有双关语意)仅跟踪非零的int值。如果JIT知道它正在做什么,检测零值可能不像听起来那么昂贵,因为如果结果为零,CPU'和'操作通常会设置'零'标志。

与所有性能优化一样,这个需要一些测量来确定它是否值得。

答案 1 :(得分:0)

如果每个决策位和位置都列为对象{bit value,k-th position},那么最终会得到一个长度为2 * k的数组。如果您链接到项目中的每个数组位置,表示为链接列表(长度为k),使用指向{bit,position}对象的指针作为节点值,您可以“使一组”简单地删除{bit,position}对象的项目。这将要求您在搜索项目列表时找到“完整”项目(这使搜索真的很慢?)。

类似于: [{0,0},{1,0},{0,1},{1,1},{0,2},{1,2},{0,3},{1,3}] < / p>

并从“0100”链接,表示为:{0-&gt; 3-> 4->&gt; 6}

在您尝试找到它们之前,您不会知道哪些项目无效(因此它并没有真正限制您的搜索空间,这就是您所追求的目标)。

哦,我试过了。

答案 2 :(得分:0)

当然,有可能(即使这是“作弊”)。只需保留一堆Remove对象:

struct Remove {
    bool set;
    int index;
}

remove函数只是在栈上推送一个对象。 Viola,O(1)。

如果你想得到想象,你的堆栈不能超过(位数)而不包含重复或不可能的场景。

无论何时撤回或迭代,集合的其余部分都必须应用逻辑。

插入集合的两种方法:

  1. 在插入时应用删除规则,以清除堆栈,在O(n)中生成。得付钱给某个地方。
    • 每个位域必须将其索引存储在移除堆栈中,以了解适用于它的规则。然后,上面的堆栈大小限制无关紧要

答案 3 :(得分:0)

如果使用数组存储二叉树,则可以快速索引任何元素(索引n处的节点的子节点位于索引(n + 1)* 2和(n + 1)* 2-1。给定级别的所有节点按顺序存储。级别x的第一个节点为2 ^ x-1,该级别有2 ^ x个元素。

不幸的是,从复杂性的角度来看,我不认为这对你有任何影响。删除一个级别的所有左节点是O(n / 2)最坏的情况,当然是O(n)。当然,实际的工作取决于你检查的是哪一位,所以平均值可能稍好一些。这也需要O(2 ^ n)内存,这比链表差得多,根本不实用。

我认为这个问题的真正要求是将一组集合有效地分成两组。使用bitset来描述该集合可以快速检查成员资格,但似乎不能使问题变得更容易。