1

我正在用 C 实现一个灵活的数组。所有想法都基于这个小论文。我的错误在于索引操作。被黑客入侵太久了,无法承认。要求有知识的头脑和眼睛。

概述:数据结构由 1 个平面索引块组成,其中包含指向数据块的指针。每个数据块的大小为 2^(k/2)。其中 k 是前导设置位。所以当我搜索元素“i”时,k 是 log_2(i+1)。

index = { 
    [0], -> [0]
    [2],  -> [0,1]
    [2],  -> [0,1]
    [3],  -> [0,1,2,3]
    [4],  -> [0,1,2,3]
    ....}

每个数据块的大小由它所聚集的“超级块”决定。其中一个超级块由相同大小的数据块组成。所以。索引 1,2 在同一个超级块(超级块 1)中。而 0(超级块 0)和索引 3(超级块 2)则不是。

最终每个超级块都有 2^(floor(k/2)) 个数据块,每个数据块的大小为 2^(ceil(k/2))。

问题 当 r = 2 的幂时,索引会跳过应该是什么。就像我搜索 3 它应该是index [2][0],而不是它的index[3][0]. 为什么会这样?有什么办法可以避免吗?我没有看到“关闭 1”错误吗?

这里的代码 是单个 main 函数testcase,它清晰简单,在尝试获取索引 3 处的元素时失败;

定位索引 i 的简化代码如下:

/* Edited from the actual test case for extra clarity */
/* all vars int */
r= i+1
k = first_set_bit(r) - 1;   // ex: r = 5, 5 = "00000101"b so k is (3-1) = 2

b = first_subset_of_r(floor(k/2)); // floor(k/2) bits of r immediately after first set; 
e = last_subset_of_r(ceil(k/2); // last ceil(k/2) bits of r 
p = (1 << k) -1 ; // 2^k -1
// Index supposed to be found with. . .
return index[p+b][e];

这是一些真实的输出,首先通过索引和数据块打印数组的内容,然后 4 的输出尝试进入索引

第一部分转储二维数组,其中 bar 之前的数字是索引块的索引,之后的部分是索引块指向的数组中包含的元素。所有数组都是零索引的。

[clemensm@gaia:23]> ./a.out 
Index block |  element number (Not it's index!)
0 | 0   
1 | 1   2   
2 | 3   4   
3 | 5   6   
4 | 7   8   9   10  
5 | 11  12  13  14  
6 | 15  16  17  18  
7 | 19  20  21  22  
8 | 23  24  25  26  
9 | 27  28  29  30  
10 | 31  32  33  34  35  36  37  38  
11 | 39  40  41  42  43  44  45  46  
12 | 47  48  49  50  51  52  53  54  
13 | 55  56  57  58  59  60  61  62  
14 | 63  64  65  66  67  68  69  70  
15 | 71  72  73  74  75  76  77  78  
16 | 79  80  81  82  83  84  85  86  
17 | 87  88  89  90  91  92  93  94  
18 | 95  96  97  98  99  100 101 102 
19 | 103 104 105 106 107 108 109 110 
Finished element dump

Trying to get 0
R: [1]b
k/2=[0], Ceil(k,2)=[0]
K: [0] is the leading 1 bit
B: [0]
E: [0]
P: [0] data blocks prior to our superblock
p+b,e : [0,0] 

Trying to get 1
R: [10]b
k/2=[0], Ceil(k,2)=[1]
K: [1] is the leading 1 bit
B: [0]
E: [0]
P: [1] data blocks prior to our superblock
p+b,e : [1,0] 

Trying to get 2
R: [11]b
k/2=[0], Ceil(k,2)=[1]
K: [1] is the leading 1 bit
B: [0]
E: [1]
P: [1] data blocks prior to our superblock
p+b,e : [1,1] 

Trying to get 3
R: [100]b
k/2=[1], Ceil(k,2)=[1]
K: [2] is the leading 1 bit
B: [0]
E: [0]
P: [3] data blocks prior to our superblock
p+b,e : [3,0] 
a.out: test_array.c:81: main: Assertion `get_index(3)==3' failed.
Abort (core dumped)
4

1 回答 1

3

只是为了搜索引擎和清晰,论文名称:Resizable Arrays in Optimal Time and Space
就我从论文中看到的,你可以在这里做一个非常基本的体验。即使它看起来很漂亮,也可能有错误。

定位算法显然是错误的。第 3 行声称要计算之前的数据块数,SB_k这种方式是行不通的。您已经在上面的示例中看到了这一点。

我建议自己找出公式,然后继续阅读。

我的分析表明了这个公式:

p = 2^(k/2 + 1) - 2 + (k mod 2) * 2^(k/2) # "/" indicates integer division

示例代码:

int max = 20;
for (int k = 0; k < max; k++)
    System.out.println("k=" + k + " - data blocks: " + Math.pow(2, k/2) + " - data blocks size: " + Math.pow(2, Math.ceil(k/2.0)));
System.out.println();

for (int i = 0; i < max; i++) {
    String r = Integer.toBinaryString(i + 1);
    int k = r.length() - 1;
    String b_bin = r.substring(1, 1 + k/2);
    int b = Integer.parseInt("0" + b_bin, 2);
    String e_bin = r.substring((int)Math.ceil(k / 2.0));
    int e = Integer.parseInt("0" + e_bin, 2);
    int p = (1 << (k/2 + 1)) - 2 + (k & 1) * (1 << (k/2));
    int db = p + b;
    System.out.println("i=" + i + ", r=" + r + ", k=" + k + ", b_bin=" + b_bin + ", b=" + b + ", e_bin=" + e_bin + ", e=" + e + ", p=" + p + ", db=" + db);
}

样本结果:

k=0 - data blocks: 1.0 - data blocks size: 1.0
k=1 - data blocks: 1.0 - data blocks size: 2.0
k=2 - data blocks: 2.0 - data blocks size: 2.0
k=3 - data blocks: 2.0 - data blocks size: 4.0
k=4 - data blocks: 4.0 - data blocks size: 4.0
k=5 - data blocks: 4.0 - data blocks size: 8.0
k=6 - data blocks: 8.0 - data blocks size: 8.0
k=7 - data blocks: 8.0 - data blocks size: 16.0
k=8 - data blocks: 16.0 - data blocks size: 16.0
k=9 - data blocks: 16.0 - data blocks size: 32.0
k=10 - data blocks: 32.0 - data blocks size: 32.0
k=11 - data blocks: 32.0 - data blocks size: 64.0
k=12 - data blocks: 64.0 - data blocks size: 64.0
k=13 - data blocks: 64.0 - data blocks size: 128.0
k=14 - data blocks: 128.0 - data blocks size: 128.0
k=15 - data blocks: 128.0 - data blocks size: 256.0
k=16 - data blocks: 256.0 - data blocks size: 256.0
k=17 - data blocks: 256.0 - data blocks size: 512.0
k=18 - data blocks: 512.0 - data blocks size: 512.0
k=19 - data blocks: 512.0 - data blocks size: 1024.0

i=0, r=1, k=0, b_bin=, b=0, e_bin=1, e=1, p=0, db=0
i=1, r=10, k=1, b_bin=, b=0, e_bin=0, e=0, p=1, db=1
i=2, r=11, k=1, b_bin=, b=0, e_bin=1, e=1, p=1, db=1
i=3, r=100, k=2, b_bin=0, b=0, e_bin=00, e=0, p=2, db=2
i=4, r=101, k=2, b_bin=0, b=0, e_bin=01, e=1, p=2, db=2
i=5, r=110, k=2, b_bin=1, b=1, e_bin=10, e=2, p=2, db=3
i=6, r=111, k=2, b_bin=1, b=1, e_bin=11, e=3, p=2, db=3
i=7, r=1000, k=3, b_bin=0, b=0, e_bin=00, e=0, p=4, db=4
i=8, r=1001, k=3, b_bin=0, b=0, e_bin=01, e=1, p=4, db=4
i=9, r=1010, k=3, b_bin=0, b=0, e_bin=10, e=2, p=4, db=4
i=10, r=1011, k=3, b_bin=0, b=0, e_bin=11, e=3, p=4, db=4
i=11, r=1100, k=3, b_bin=1, b=1, e_bin=00, e=0, p=4, db=5
i=12, r=1101, k=3, b_bin=1, b=1, e_bin=01, e=1, p=4, db=5
i=13, r=1110, k=3, b_bin=1, b=1, e_bin=10, e=2, p=4, db=5
i=14, r=1111, k=3, b_bin=1, b=1, e_bin=11, e=3, p=4, db=5
i=15, r=10000, k=4, b_bin=00, b=0, e_bin=000, e=0, p=6, db=6
i=16, r=10001, k=4, b_bin=00, b=0, e_bin=001, e=1, p=6, db=6
i=17, r=10010, k=4, b_bin=00, b=0, e_bin=010, e=2, p=6, db=6
i=18, r=10011, k=4, b_bin=00, b=0, e_bin=011, e=3, p=6, db=6
i=19, r=10100, k=4, b_bin=01, b=1, e_bin=100, e=4, p=6, db=7

编辑:有关公式方式的更多详细信息

如何获得公式(我建议阅读每一步并考虑是否可以自己继续,如果不能继续踩):
论文说:"When superblock SB_k is fully allocated, it consists of 2^floor(k/2) data blocks"

如果我们想要 SB_k 之前的数据块数,我们必须对所有数据块求和:

p = sum from i=0 to k-1 over 2^floor(i/2)

我们现在可以使用它,这是一个明确的 for 循环语句。但是让我们把它作为一个单一的计算。
如果您考虑总和,由于地板的原因,它每 2^i 两次求和。这对所有偶数 k 都是正确的,因为总和 max 是不均匀的,因此我们有floor((k-2)/2) = floor((k-1)/2)。对于奇数 k,我们只有一个数字,我们必须单独关心这个。

所以,我们现在有:

(2 * sum from i=0 to floor((k-2)/2) over 2^i) + (k mod 2)*2^k/2

(如果您需要更多详细信息,请为 k=6,7,8 制作一些示例)

现在,我们可以清楚地摆脱总和,因为我们知道sum from i=0 to n over 2^i = 2^(n+1) - 1(这是一个基本的数学/cs 证明。您可以从二进制表示中清楚地看到正确性)。我们使用这个公式得到:

2 * (2^(floor((k-2)/2) + 1) - 1) + (k mod 2)*2^k/2

现在,我们可以乘以 2,修改指数,我们就完成了:

2^(floor(k/2) + 1) - 2 + (k mod 2)*2^k/2

我希望这有帮助。

于 2012-10-29T23:55:04.887 回答