今天在编写一些代码时,我发生了一种情况,这种情况导致我编写了一种我以前从未见过的二进制搜索。这个二进制搜索是否有名称,它真的是一个“二进制”搜索吗?
首先,为了使搜索更容易理解,我将解释产生其创建的用例。
假设您有一个有序号码列表。系统会要求您在列表中找到最接近x的数字索引。
int findIndexClosestTo(int x);
对findIndexClosestTo()
的调用始终遵循此规则:
如果
findIndexClosestTo()
的最后结果是i
,那么接近i
的索引更有可能成为当前findIndexClosestTo()
调用的结果。
换句话说,我们需要找到的索引更接近于我们发现的最后一个索引而不是更接近索引。
举个例子,想象一个在屏幕上左右走动的模拟男孩。如果我们经常查询男孩所在位置的索引,很可能他就在我们找到他的最后一个地方附近。
鉴于上述情况,我们知道findIndexClosestTo()
的最后一个结果是i
(如果这实际上是第一次调用函数,i
默认为中间索引列表,为简单起见,虽然单独的二进制搜索以查找第一次调用的结果实际上会更快),并且该函数已被再次调用。给定新的数字x
,我们按照此算法查找其索引:
interval = 1;
x
位于i
?如果是,请返回i
; x
是高于还是低于i
。 (请记住,列表已排序。)interval
方向移动x
个索引。x
,请返回该位置。interval
。 (即interval *= 2
)x
,请返回interval
个索引,设置interval = 1
,转到4。鉴于上述概率规则(在Motivation标题下),我认为这是找到正确索引的最有效方法。你知道更快的方式吗?
答案 0 :(得分:5)
在最坏的情况下,您的算法是O((log n)^ 2)。
假设你从0开始(间隔= 1),你寻找的值实际上位于2 ^ n - 1的位置。
首先,您将检查1,2,4,8,...,2 ^(n-1),2 ^ n。哎呀,超过,所以回到2 ^(n-1)。
接下来检查2 ^(n-1)+1,2 ^(n-1)+ 2,...,2 ^(n-1)+ 2 ^(n-2),2 ^(n -1)+ 2 ^(N-1)。最后一个词是2 ^ n,所以哎呀,再次打捞。回到2 ^(n-1)+ 2 ^(n-2)。
依此类推,直到你最终达到2 ^(n-1)+ 2 ^(n-2)+ ... + 1 == 2 ^ n - 1。
第一次超调采取了log n步骤。接下来采取(log n)-1步骤。下一步(log n) - 2步。等等。
所以,最糟糕的情况是,你采取了1 + 2 + 3 + ... + log n == O((log n)^ 2)步骤。
我认为,更好的想法是在第一次超调后切换到传统的二进制搜索。这将保留算法的O(log n)最坏情况性能,而当目标确实在附近时趋于快一点。
我不知道这个算法的名称,但我确实喜欢它。 (奇怪的巧合,我昨天可以用它。真的。)
答案 1 :(得分:4)
您正在做的是(恕我直言)Interpolation search
的版本在插值搜索中,您假设数字是均匀分布的,然后您尝试从数组的第一个和最后一个数字和长度猜测数字的位置。
在您的情况下,您正在修改插值算法,以便假设Key非常接近您搜索的最后一个数字。
另请注意,您的算法类似于算法,其中TCP尝试查找最佳数据包大小。 (不记得名字:()
答案 2 :(得分:1)
您的例程是典型的插值例程。如果用随机数字(〜标准二进制搜索)调用它,你不会损失很多,但如果你用慢慢增加的数字来调用它,那么找到正确的索引就不会花费很长时间。
因此,这是搜索有序表以进行插值的合理默认行为。
这个方法在Numerical Recipes第3版第3.1节中进行了详细讨论。
答案 3 :(得分:0)
这是我的头脑,所以我没有什么可以支持它但是直觉!
在第7步,如果我们已经通过了x,那么将interval
减半可能会更快,然后回到x
- 实际上是interval = -(interval / 2)
,而不是重置{{1}到1。
我必须在纸上画出一些数字,但是......
编辑:道歉 - 我在上面说废话:不理我! (而且我会离开,这次正确的想一想......)