Char数组上的累积BitWise OR

Cumulative BitWise OR on Char Array

本文关键字:BitWise OR 数组 Char      更新时间:2023-10-16

我有一个大小为7K的长char数组。

char arr[] = "1110010011....." ; // length 7K 

我必须对窗口大小为3的数组执行累积OR。这意味着:

arr[0] | arr[1] | arr[2] ;
arr[1] | arr[2] | arr[3] ;

什么是最好的方法?我可以比O(n)做得更少,或者即使复杂性是O(n),我们如何使它更快?

如果将零一数组重新打包为一个位集,则可以更快地完成。它将快大约32倍,但仍然需要O(N)时间。此外,您可以在64位机器上使用64位单词,然后您将获得64倍的改进。

然而,请注意,对于大型N,内存带宽将成为主要瓶颈,因此只能实现8倍的改进(因为大小减少了8倍)。

这是示例代码:

int main() {
    char arr[] = "01000001011111000110010000011000111";
    int n = strlen(arr);
    //preparation: convert to bitset
    uint32_t bitset[sizeof(arr) / 32 + 3] = {0};
    for (int i = 0; i < n; i++)
      bitset[i/32] ^= (arr[i]=='1') << (i % 32);
    //solution: bit operations
    uint32_t result[sizeof(bitset) / sizeof(bitset[0])] = {0};
    for (int i = 0; i < (n + 31) / 32; i++) {
        uint32_t curr = bitset[i], next = bitset[i+1];
        result[i] = curr | (curr >> 1) | (next << 31) | (curr >> 2) | (next << 30);
    }
    printf("%sn ", arr);
    for (int i = 0; i < n+2; i++)
        printf("%d", (result[i/32] >> (i%32)) & 1);
}

更新

对于可变窗口宽度W,上述方法花费O(N W)时间。对于小的W,它是最快的,但对于大的W

注意,对于任何窗口大小,问题都可以在O(N)时间内解决。例如,您可以在O(N)时间内预先计算零/一数组的前缀和。然后,对于每个窗口,可以将O(1)时间内窗口内的个数确定为两个和值的差。因此,您得到了一个简单的O(N)解决方案。它不使用任何位集,是真正大的W的最快方法。

对于中等窗口大小(如W=16),可以修改基于位集的方法,使其在O(N log W)时间内工作,这可能比O(N W)版本更快。这种方法有点类似于并行还原。以下是W=13的示例代码:

for (int i = 0; i < (n + 31) / 32; i++) {
    uint64_t curr = *(uint64_t*)&bitset[i];
    curr |= (curr >> 1);
    curr |= (curr >> 2);
    curr |= (curr >> 4);
    curr |= (curr >> 5);
    result[i] = uint32_t(curr);
}

如果你有一个大小为N的数组,它只包含0和1,并且你想要对每个K个项目进行"或"运算的结果(其中K是窗口大小),那么你所要做的就是跟踪最后一个"1"在哪里。

int last1 = -1;
int range_start = 0;
int range_end = window_size - 1;
for (int i = 0; i < array_size; ++i)
{
    if (a[i] == '1')
    {
        last1 = i;
    }
    if (i == range_end)
    {
        if (last1 >= range_start)
            // output 1
        else
            // output 0
    }
    ++range_start;
    ++range_end;
}

这里的想法是,如果窗口中有一个或多个1,那么任何窗口大小的累积OR都将为1。如果窗口包含所有0,则结果为0。

您可以通过在单独的循环中查看第一个window_size - 1值来对其进行一点优化,从而消除range_end变量,但这会使循环稍微复杂一些。我不知道这是否会是一场净胜球。

为了澄清,您希望输出数组中有n个元素,每个元素的值为arr[n-1]|arr[n]|arr[n+1]。(第一个和最后一个元素可能除外,它们分别没有arr[n-1]和arr[n+1]。

如果这是正确的,那么在小于O(n)的时间内不可能做到这一点。您需要至少查看数组中的每个元素一次,这单独需要O(n)时间。

幸运的是,即使是最天真的方法也能达到O(n)的目标:

int size = strlen(arr);
char arr2[size];
for (int i=1; i<size-1; i++) { //ignore first and last element
    if (arr[i-1] == '1' || arr[i] == '1' || arr[i+2] == '1') {
        arr2[i] = '1';
    } else {
        arr2[i] = '0';
    }
}

在这一点上,你必须决定你所说的"高效"是什么意思。您需要决定是要减少比较还是减少分配。根据您的情况,这两种方法中的任何一种都可能是有效的选择,并会产生非常不同的解决方案。