Algorithm 如何有效地从Fenwick树中查找连续范围的已用/空闲插槽

Algorithm 如何有效地从Fenwick树中查找连续范围的已用/空闲插槽,algorithm,data-structures,fenwick-tree,range-query,Algorithm,Data Structures,Fenwick Tree,Range Query,假设我正在跟踪Fenwick树中插槽的使用情况。作为一个例子,让我们考虑跟踪32个时隙,导致芬威克树布局,如下面的图像中所示,其中网格中的数字指示了由芬威克树操纵的基础数组中的索引,其中每个单元中的值是该段中的“已使用”项的总和。(即阵列单元23存储[16-23]范围内的已用插槽数量)。最低级别的项目(即单元0、2、4……)只能具有“1”(已用插槽)或“0”(空闲插槽)的值 我要寻找的是一种有效的算法,用于找到给定数量的连续空闲时隙的第一个范围 为了举例说明,假设下图中显示了Fenwick树,

假设我正在跟踪Fenwick树中插槽的使用情况。作为一个例子,让我们考虑跟踪32个时隙,导致芬威克树布局,如下面的图像中所示,其中网格中的数字指示了由芬威克树操纵的基础数组中的索引,其中每个单元中的值是该段中的“已使用”项的总和。(即阵列单元23存储[16-23]范围内的已用插槽数量)。最低级别的项目(即单元0、2、4……)只能具有“1”(已用插槽)或“0”(空闲插槽)的值

我要寻找的是一种有效的算法,用于找到给定数量的连续空闲时隙的第一个范围

为了举例说明,假设下图中显示了Fenwick树,其中总共使用了9个插槽(请注意,浅灰色的数字只是为了清晰起见而添加的,而不是实际存储在树的数组单元格中)

现在,我想找到例如10个空闲插槽的第一个连续范围,该范围应为:

我似乎找不到一种有效的方法来实现这一点,这让我有点头疼。请注意,由于所需的存储空间对于我的目的至关重要,我不希望将设计扩展为段树

任何关于O(logn)类型解决方案的想法和建议都是非常受欢迎的

编辑

赏金期结束后,是时候更新了。感谢所有的评论、问题、建议和回答。它们让我重新思考,教会了我很多东西,并向我指出(有一天我可能会学到这一课),在提问时,我应该更加关注我想解决的问题

因为他是唯一一个对包括请求的代码/伪代码在内的问题提供合理答案的人,他将获得赏金

他还正确地指出,使用这种结构进行O(logn)搜索是不可能的。很荣幸提供了一个让我想到最坏情况性能的证据

的评论和回答让我意识到我应该用不同的方式来表述我的问题。事实上,我已经在很大程度上按照他的建议去做了(见-这说明了我在发布这个问题之前遇到的困难),并提出了这个问题,希望有一种有效的方法来搜索连续范围,从而防止将此设计更改为段树(需要额外的1024字节)。然而,这样的更改似乎是明智之举


对于任何感兴趣的人,一个二进制编码的Fenwick树与本问题中使用的示例相匹配(32槽64位编码的Fenwick树)可以在这里找到:。

当搜索K个连续槽的范围时,一个快速检查是找到两个小于或等于K/2的最大幂。任何K个连续零槽必须至少包含一个大小的芬威克对齐槽范围,如mcdowella在中所建议的,让K2=K/2,向上舍入,让M为2的最小幂is>=K2。一种很有希望的方法是搜索一个size-M块中完全包含K2零的连续块,一旦我们找到这些块,检查相邻size-M块,看看它们是否包含足够的相邻零。对于初始扫描,如果块中的0数=K2,并且块的大小>=2*M,我们可以查看两个子块

这表明了下面的代码。下面,A[0..N-1]是Fenwick树数组;N被假定为2的幂。我假设您正在计算空插槽,而不是非空插槽;如果您更喜欢计算空插槽,则很容易从一个转换到另一个

initialize q as a stack data structure of triples of integers
push (N-1, N, A[n-1]) onto q
# An entry (i, j, z) represents the block [i-j+1 .. i] of length j, which
# contains z zeroes; we start with one block representing the whole array.
# We maintain the invariant that i always has at least as many trailing ones
# in its binary representation as j has trailing zeroes. (**)
initialize r as an empty list of pairs of integers
while q is not empty:
    pop an entry (i,j,z) off q
    if z < K2:
        next

    if FW(i) >= K:
        first_half := i - j/2
        # change this if you want to count nonempty slots:
        first_half_zeroes := A[first_half]
        # Because of invariant (**) above, first_half always has exactly
        # the right number of trailing 1 bits in its binary representation
        # that A[first_half] counts elements of the interval
        # [i-j+1 .. first_half].

        push (i, j/2, z - first_half_zeroes) onto q
        push (first_half, j/2, first_half_zeroes) onto q
    else:
        process_block(i, j, z)
将q初始化为三重整数的堆栈数据结构
将(N-1,N,A[N-1])推到q上
#条目(i,j,z)表示长度为j的块[i-j+1..i],其
#包含z零;我们从一个表示整个数组的块开始。
#我们保持不变,我总是有至少一样多的尾随数
#在其二进制表示形式中,j有尾随的零。(**)
将r初始化为整数对的空列表
q不是空的:
在q上弹出一个条目(i,j,z)
如果z=K:
前半部分:=i-j/2
#如果要计算非空插槽数,请更改此选项:
前半部分零:=A[前半部分]
#由于上面的不变量(**),前半部分总是
#二进制表示法中尾随1位的正确数目
#[前半部分]计算区间的元素
#[i-j+1.上半部分]。
将(i,j/2,z-前半个零)推到q上
将(前半部分,j/2,前半部分)推到q上
其他:
工艺块(i、j、z)
这使我们能够以至少K/2个零的顺序处理所有大小为M的块。您甚至可以将上半部分和下半部分推到q上的顺序随机化,以便以随机顺序获得块,这可能有助于解决阵列上半部分比下半部分填充快得多的情况

现在我们需要讨论如何处理单个块。如果z=j,则该块完全用0填充,我们可以左右查看以添加零。否则,我们需要确定它是否以>=K/2连续零开始,如果是以确切的数量开始,然后检查前一个块是否以适当数量的零结束。Simil首先,我们检查块是否以>=K/2个连续的零结束,如果是,则检查确切的数字,然后检查下一个块是否以适当数量的零开始。因此,我们需要一个过程来找到一个块开始或结束的零的数量,如果它至少是a或最多是b,则可能有一个快捷方式。准确地说,让_以_零结束s(i,j,min,max)是一个过程
def process_block(i, j, z):
    if j == z:
        if i > j:
            a := ends_with_zeroes(i-j, j, 0, K-z)
        else:
            a := 0

        if i < N-1:
            b := starts_with_zeroes(i+j, j, K-z-a-1, K-z-a)
        else:
            b := 0

        if b >= K-z-a:
            print "Found: starting at ", i - j - a + 1
        return

    # If the block doesn't start or end with K2 zeroes but overlaps with a
    # correct solution anyway, we don't need to find it here -- we'll find it
    # starting from the adjacent block.
    a := starts_with_zeroes(i, j, K2-1, j)
    if i > j and a >= K2:
        b := ends_with_zeroes(i-j, j, K-a-1, K-a)
        if b >= K-a:
            print "Found: starting at ", i - j - a + 1
        # Since z < 2*K2, and j != z, we know this block doesn't end with K2
        # zeroes, so we can safely return.
        return

    a := ends_with_zeroes(i, j, K2-1, j)
    if i < N-1 and a >= K2:
        b := starts_with_zeroes(i+j, K-a-1, K-a)
        if b >= K-a:
            print "Found: starting at ", i - a + 1
def starts_with_zeroes(i, j, min, max):
    start := i-j

    h2 := 1
    while h2 * 2 <= min:
        h2 := h2 * 2
        if A[start + h2] < h2:
            return min
    # Now h2 = 2^h in the text.
    # If you insist, you can do the above operation faster with bit twiddling
    # to get the 2log of min (in which case, for more info google it).

    while h2 < max and A[start + 2*h2] == 2*h2:
        h2 := 2*h2
    if h2 == j:
        # Walk up the Fenwick tree to determine the exact number of zeroes
        # in interval [start+1 .. i]. (Not implemented, but easy.) Let this
        # number be z.

        if z < j:
            h2 := h2 / 2

    if h2 >= max:
        return max

    # Now we know that [start+1 .. start+h2] is all zeroes, but somewhere in 
    # [start+h2+1 .. start+2*h2] there is a one.
    # Maintain invariant: the interval [start+1 .. start+h2] is all zeroes,
    # and there is a one in [start+h2+1 .. start+h2+step].
    step := h2;
    while step > 1:
        step := step / 2
        if A[start + h2 + step] == step:
             h2 := h2 + step
    return h2