【问题标题】:Cumulative BitWise OR on Char Array字符数组上的累积位或
【发布时间】:2015-10-30 04:31:20
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • C 还是 C++?它们是不同的语言。
  • 根据您的示例,您需要遍历所有“n”个元素。 O(n) 将是复杂度
  • C++ @Nandu , 为什么-1 我不明白你发现了什么问题.. 同时当我说即使复杂度是 O(n) 我们怎么能做得更快..
  • 顺便说一句,我没有投反对票。如果复杂度为 O(n) 并且您同意,那么您所说的更快是什么意思?你在找 O(logn) 吗??
  • 我预计重用操作结果会显着提高速度。有一种常用的“求和”技术,可逆运算,如数字+ - 按位计算或不可逆。 如果你有一个大小相同的额外数组(例如,保存结果),你能从先做成对的得到什么吗? (在现实生活中,像这样保存操作甚至可能会损害性能 - 这可能是学习避免基准测试中最简单的谬误的机会。)

标签: c++ c arrays algorithm bitwise-operators


【解决方案1】:

如果你将你的零一数组重新打包成一个 bitset,那么你可以更快地完成它。它会快大约 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("%s\n ", 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) 时间内为您的零/一数组预先计算 prefix sums。然后对于每个窗口,可以在 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);
}

【讨论】:

  • 我认为您可以更快地获得result 将所有内容转换为uint64_t 数组(或uint128_t,如果可能)并使用位掩码对curr 执行按位-&amp;移位和&gt; 0 比较,因为该示例只是按位或在单个位上进行-如果任何一个非零,则结果为1,否则结果为0。您需要处理转换为next 但翻转到 next 应该不会对性能产生太大影响。这将消除循环中的 3 个班次操作,并完全消除第一个循环。
  • @AndrewHenle:我假设从一开始就可以将位数组存储在位集中。所以第一个循环只是为了说明,不管它有多慢。我不明白你的建议。如果它真的不同,也许你应该把它作为另一个答案发布。
  • 很高兴知道如果我的窗口大小也不同会怎样,尽管三个可以从 3 到 100 不等
  • @KumarVishal:我已经用另外两种方法更新了解决方案。您可以自行检查哪种尺寸的速度更快。
  • @stgatilov 为您提供解决方案
【解决方案2】:

如果您有一个大小为 N 的数组,其中仅包含 0 和 1,并且您想要对每个 K 项进行 ORing 的结果(其中 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 变量,但这会使您的循环稍微复杂一些。我不知道这是否会是一场净胜。

【讨论】:

    【解决方案3】:

    澄清一下,您希望输出数组中包含 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';
        }
    }
    

    此时,您必须确定“高效”的含义。您需要决定是否要减少比较或分配。根据您的情况,其中任何一个都可能是一个有效的选择,并且会导致非常不同的解决方案。

    【讨论】:

    • 我对空间不太在意,这对我来说没问题。我可以将它存储在完全不同的数组中。
    • OP 想要更快的算法。没有一种可以节省空间
    猜你喜欢
    • 2016-05-06
    • 2021-06-15
    • 1970-01-01
    • 2017-04-02
    • 2023-01-20
    • 2015-11-27
    • 2017-08-03
    • 2019-05-04
    • 2018-12-29
    相关资源
    最近更新 更多