gpt4 book ai didi

c++ - 特定的二元置换生成函数

转载 作者:行者123 更新时间:2023-11-28 05:51:45 28 4
gpt4 key购买 nike

所以我正在编写一个程序,我需要生成二进制数的字符串,这些字符串不仅具有特定的长度,而且还具有特定数量的 1 和 0。此外,将生成的这些字符串与更高和更低的值进行比较,以查看它们是否在该特定范围内。我遇到的问题是我正在处理 64 位无符号整数。因此,有时,需要所有 64 位的非常大的数字会为根本不在范围内的值产生大量二进制字符串排列,这需要大量时间。

我很好奇算法是否有可能接受两个绑定(bind)值,多个绑定(bind)值,并且只在具有特定数量的绑定(bind)值之间生成二进制字符串。

这是我到目前为止所拥有的,但它产生了很多数字。

void generatePermutations(int no_ones, int length, uint64_t smaller, uint64_t larger, uint64_t& accum){

char charArray[length+1];

for(int i = length - 1; i > -1; i--){
if(no_ones > 0){
charArray[i] = '1';
no_ones--;
}else{
charArray[i] = '0';
}
}
charArray[length] = '\0';

do {
std::string val(charArray);
uint64_t num = convertToNum(val);
if(num >= smaller && num <= larger){
accum ++;
}
} while ( std::next_permutation(charArray, (charArray + length)));

}

最佳答案

(注:二进制值中 1 位的个数一般称为总体计数——popcount,简称 popcount——或汉明权重。)

有一个众所周知的 bit-hack 循环遍历具有相同人口计数的所有二进制字,它基本上执行以下操作:

  • 找到单词的最长后缀,它包含一个 0,一个非空的 1 序列,最后是一个可能为空的 0 序列。
  • 将第一个 0 改为 1;下面的 1 到 0,然后将所有其他 1(如果有)移到单词的末尾。

  • 例子:
    00010010111100
    ^-------- beginning of the suffix
    00010011 0 becomes 1
    0 1 becomes 0
    00111 remaining 1s right-shifted to the end

    这可以通过使用 x 中的最低设置位这一事实快速完成。是 x & -x (其中 - 表示 x 的 2s 补码负数)。要找到后缀的开头,只需将最低位设置位添加到数字中,然后找到新的最低位设置位即可。 (用几个数字试试这个,你应该会看到它是如何工作的。)

    最大的问题是执行右移,因为我们实际上并不知道位数。传统的解决方案是用除法(原始低位 1 位)进行右移,但事实证明,现代硬件上的除法相对于其他操作数来说确实很慢。循环一位移位通常比除法要快,但在下面的代码中,我使用 gcc 的 __builtin_ffsll ,如果目标硬件上存在一个操作码,它通常会编译成适当的操作码。 (详情见 man ffs ;我使用内置函数来避免功能测试宏,但它有点难看,并且限制了您可以使用的编译器范围。OTOH, ffsll 也是一个扩展。)

    为了便于携带,我还包括了基于部门的解决方案;但是,在我的 i5 笔记本电脑上花费的时间几乎是我的三倍。
    template<typename UInt>
    static inline UInt last_one(UInt ui) { return ui & -ui; }

    // next_with_same_popcount(ui) finds the next larger integer with the same
    // number of 1-bits as ui. If there isn't one (within the range
    // of the unsigned type), it returns 0.
    template<typename UInt>
    UInt next_with_same_popcount(UInt ui) {
    UInt lo = last_one(ui);
    UInt next = ui + lo;
    UInt hi = last_one(next);
    if (next) next += (hi >> __builtin_ffsll(lo)) - 1;
    return next;
    }

    /*
    template<typename UInt>
    UInt next_with_same_popcount(UInt ui) {
    UInt lo = last_one(ui);
    UInt next = ui + lo;
    UInt hi = last_one(next) >> 1;
    if (next) next += hi/lo - 1;
    return next;
    }
    */

    剩下的唯一问题是在给定范围内找到第一个具有正确 popcount 的数字。为了解决这个问题,可以使用以下简单算法:
  • 从范围中的第一个值开始。
  • 只要值的 popcount 太高,通过将低位 1 位添加到数字来消除最后一次运行的 1(使用与上面完全相同的 x&-x 技巧)。由于这是从右到左工作的,它不能循环超过 64 次,每比特一次。
  • 虽然 popcount 太小,但通过将低位 0 位更改为 1 来添加可能的最小位。由于这会在每个循环中添加一个 1 位,因此它也不能循环超过 k次(其中 k 是目标人口计数),并且不需要在每个循环上重新计算人口计数,这与第一步不同。

  • 在下面的实现中,我再次使用 GCC 内置函数 __builtin_popcountll .这个没有对应的 Posix 函数。见 Wikipedia page对于替代实现和支持该操作的硬件列表。请注意,找到的值可能会超出范围的末尾;此外,该函数可能返回一个小于提供的参数的值,表明没有合适的值。因此,您需要在使用前检查结果是否在所需范围内。
    // next_with_popcount_k returns the smallest integer >= ui whose popcnt
    // is exactly k. If ui has exactly k bits set, it is returned. If there
    // is no such value, returns the smallest integer with exactly k bits.
    template<typename UInt>
    UInt next_with_popcount_k(UInt ui, int k) {
    int count;
    while ((count = __builtin_popcountll(ui)) > k)
    ui += last_one(ui);
    for (int i = count; i < k; ++i)
    ui += last_one(~ui);
    return ui;
    }

    通过将第一个循环更改为:
    while ((count = __builtin_popcountll(ui)) > k) {
    UInt lo = last_one(ui);
    ui += last_one(ui - lo) - lo;
    }

    这减少了大约 10% 的执行时间,但我怀疑该函数是否会被频繁调用以使其值得。根据您的 CPU 实现 POPCOUNT 操作码的效率,使用单个位扫描执行第一个循环可能会更快,以便能够跟踪 popcount 而不是重新计算它。在没有 POPCOUNT 操作码的硬件上几乎肯定会出现这种情况。

    一旦你有了这两个函数,迭代一个范围内的所有 k 位值就变得微不足道了:
    void all_k_bits(uint64_t lo, uint64_t hi, int k) {
    uint64_t i = next_with_popcount_k(lo, k);
    if (i >= lo) {
    for (; i > 0 && i < hi; i = next_with_same_popcount(i)) {
    // Do what needs to be done
    }
    }
    }

    关于c++ - 特定的二元置换生成函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35115478/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com