使用O(log2(amount_bits))中的二进制搜索来查找floor(log2(int))时遇到问题

时间:2019-03-27 13:10:27

标签: c++ algorithm binary

在我们的算法课上,教授在实验课上还有一个问题。在log2(n)步骤(例如,当T = uint64_t时,则n = 64)中找到n位整数的底数(log2(x))。

我们发现我们应该能够通过二进制搜索解决此问题,但是在某些情况下,我们得到的结果是1或连续循环。我们已经花了一段时间,但似乎无法做到这一点。我们如何最好地应对呢?我们尝试使用讨论过的here的不变技巧进行推理,但似乎比这复杂一些。例如。对于十进制数,很难在位7或6之间进行选择,因为128大于100,而64较小。不幸的是,在缓解这种情况时,我们打破了一些极端情况。

编辑:如下所述,这纯粹是一个学术问题,在现实情况中可用性很小甚至没有。

到目前为止,这是我们的代码:

//
//   h      l
//   76543210
// 0b01000001 = 65
//

using T = unsigned char;

int lgfloor(T value)
{
    assert(value > 0);

    int high = ((sizeof(value) * 8) - 1);
    int low = 0;
    int mid = 0;
    T guess = 0;

    while (high > low)
    {
        mid = (low + ((high - low) / 2));
        guess = static_cast<T>(1) << mid;

        printf("high: %d, mid: %d, low: %d\n", high, mid, low);

        if (value < guess)
        {
            high = mid - 1;
        }
        else
        {
            low = mid;
        }
    }

    return low;
}

我们已经创建了以下单元测试(使用GoogleTest):

TEST(LgFloor, lgfloor)
{
    ASSERT_DEATH(lgfloor(-1), "Assertion `value > 0' failed.");
    ASSERT_DEATH(lgfloor(0), "Assertion `value > 0' failed.");

    ASSERT_EQ(lgfloor(1), 0);
    ASSERT_EQ(lgfloor(2), 1);
    ASSERT_EQ(lgfloor(64), 6);
    ASSERT_EQ(lgfloor(100), 6);
}

预先感谢,

马丁

4 个答案:

答案 0 :(得分:1)

您需要适当的退出条件。假设y = floor(lg2(x))。您应在2^low <= xx < 2^(low+1)时退出循环。但是,如果high == low+1可以实现,但是您当前不会退出。只要做:

while (high > low+1)
{

最好查看循环中的不变量。例如,我们可以尝试维护x < 2^high(这需要从sizeof(T)*8开始,而不是sizeof(T)*8 - 1)。然后,您所需要做的就是平分,直到low == high-1完成。

我们可以通过仅将high(即mid)的x < 2^mid更改为value < guess来保持此不变性。那是第一种情况:

if (value < guess)
  high = mid;

我们还必须维持2^low <= x = value。因此,在else分支中(需要2^mid == guess < value,我们可以安全地设置low = mid

else
  low = mid;

剩下的就是证明循环始终在进行。从high > low+1开始,我们有了high - low >= 2,因此有了mid != lowmid != high。显然,我们将每次迭代的间隔减少了一半。

所以你去了

int lgfloor(T value)
{
    assert(value > 0);

    int high = (sizeof(value) * 8);
    int low = 0;

    while (high > low+1)
    {
        int mid = (low + ((high - low) / 2));
        T guess = static_cast<T>(1) << mid;

        printf("high: %d, mid: %d, low: %d\n", high, mid, low);

        if (value < guess)
            high = mid;
        else
            low = mid;
    }

    return low;
}

我当然应该注意到,现代硬件中有专门用于此确切目的的内在函数。例如,在Intel's intrinsics guide中搜索_BitScanReverse,该操作将在上述代码完成的一小部分周期内完成。

在处理固定宽度类型(例如C ++的整数类型)时,依赖位宽的一种或另一种渐近运行时几乎没有任何意义(尽管问题仍然具有教育意义)。

答案 1 :(得分:0)

无休止的循环归因于此行:

 mid = (low + ((high - low) / 2));

如果highlow相差1,则结果可能是mid == low,然后在while循环内导致low = mid的情况下,您进行了检查永远一样的状况。我的建议是,如果循环中有low = mid,则必须确保您的mid != low在这种情况下。因此,只需在分配作业前检查此内容,然后执行low = mid+1即可。

答案 2 :(得分:0)

必须在lg(n)个步骤中找到解决方案,这意味着诸如low= 0high= 32之类的初始化将不起作用,因为它将需要执行5个步骤每种情况,并且不适用于x大于2^32的情况。正确的解决方案必须结合首先在 geometric 搜索中将指数加倍的方法,然后再进行标准的二分法搜索。

# Geometric search
low= 0
high= 1
while (1 << high) <= x:
    low= high
    high+= high

# Dichotomic search
while high - low > 1:
    mid= (high + low) >> 1
    if x < mid:
        high= mid
    else:
        low= mid

答案 3 :(得分:-1)

好像您只需要将其移至正确的“对数”时间,直到您获得“ 1”即可。

using T = unsigned char;

int lgfloor(T value)
{
  assert(value > 0);

  int log = 0;
  while(value != 1) {
    value >> 1;
    log++;
  }
  return log;
}