在我们的算法课上,教授在实验课上还有一个问题。在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);
}
预先感谢,
马丁
答案 0 :(得分:1)
您需要适当的退出条件。假设y = floor(lg2(x))
。您应在2^low <= x
和x < 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 != low
和mid != 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));
如果high
和low
相差1,则结果可能是mid == low
,然后在while循环内导致low = mid
的情况下,您进行了检查永远一样的状况。我的建议是,如果循环中有low = mid
,则必须确保您的mid != low
在这种情况下。因此,只需在分配作业前检查此内容,然后执行low = mid+1
即可。
答案 2 :(得分:0)
必须在lg(n)
个步骤中找到解决方案,这意味着诸如low= 0
,high= 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;
}