单比较每迭代二进制搜索的要点是什么?你能解释它是如何工作的吗?如何更好地理解每比较一次比较二进制搜索?
回答
二元搜索有两个原因,每次迭代一次比较。性能不太重要的是 。提前检测完全匹配,每次迭代使用两个 比较节省循环的平均一次迭代,而 (假设比较涉及重要工作),每次迭代比较一次 ,几乎减半了每次迭代完成的工作。
二进制搜索一个整数数组,它可能无论如何没有什么区别 。即使是相当昂贵的比较,渐近地表现也是一样的,在大多数情况下,可能不会有价值追求的是一半而不是一个负值。此外,昂贵的比较常常被编码为返回负数,零或正的<
,==
或>
的函数,因此无论如何你都可以得到两者的比较。
每次迭代比较一次进行二分搜索的重要原因是 ,因为您可以获得比仅仅一些等号匹配更有用的结果。主要 搜索你可以做的是...
- 一键>目标
- 一键> =目标
- 一键==目标
- 最后关键<目标
- 最后关键< =目标
- 最后一个关键==目标
这些都减少到相同的基本算法。理解这个足够好的 ,你可以很容易地编写所有的变体并不困难,但我没有真正看到一个很好的解释 - 只有伪代码和数学证明。这是我试图解释的 。
有一些游戏的想法是尽可能接近目标 没有超调。将其改为“下冲”,这就是“Find First>”所做的。考虑搜索过程中某个阶段的范围...
| lower bound | goal | upper bound
+-----------------+-------------------------+--------------
| Illegal | better worse |
+-----------------+-------------------------+--------------
仍然需要搜索当前上限和下限之间的范围。 我们的目标是(通常)在某处,但我们还不知道在哪里。 关于上限以上项目的一个有趣的观点是,他们在 合法的意义上说他们比目标更重要。我们可以说,当前上限以上的项目 是我们迄今为止最好的解决方案。我们甚至可以在一开始就说这个 ,尽管在那个位置可能没有物品 - 从 的意义上说,如果没有有效的范围内解决方案,那么不是 的最佳解决方案就是过去的上限。
在每次迭代时,我们选择一个项目来比较上下限。 对于二分查找,这是一个舍入的中途项目。对于二叉树搜索,它是由树的结构决定的 。原理是一样的。
由于我们正在搜索的项目大于我们的目标,我们使用Item [testpos] > goal
比较测试项目 。如果结果是错误的,我们会超出(或低于 )我们的目标,所以我们保留现有的最佳解决方案,并调整我们的下限。如果结果是真的,我们已经找到了一个新的最好的解决方案,所以我们调整上限以反映这一点。无论采用哪种方式,我们都不希望再次比较该测试项目,因此我们调整我们的 范围以消除(仅仅是)从测试项目中搜索的范围。因为这个不小心,通常会导致无限循环。
通常,使用半开范围 - 包含下限和独占上限。使用此系统,上限索引处的项目不在 搜索范围内(至少不是现在),但它是是最好的解决方案。 移动下限时,将其移动到testpos+1
(以排除您仅从 测试的项目)。当你移动上限时,你将它移动到 testpos(无论如何,上限是独占的)。
if (item[testpos] > goal)
{
// new best-so-far
upperbound = testpos;
}
else
{
lowerbound = testpos + 1;
}
在上限和下限之间的范围内是空的(采用半开, 当两个具有相同指数),你的结果是你最近最迄今为止在 液,只需在你上面的上限(即在半开放的 的上限索引处)。
,所以该完整的算法是...
while (upperbound > lowerbound)
{
testpos = lowerbound + ((upperbound-lowerbound)/2);
if (item[testpos] > goal)
{
// new best-so-far
upperbound = testpos;
}
else
{
lowerbound = testpos + 1;
}
}
若要更改first key > goal
到first key >= goal
,你从字面上切换 比较运营商在if
线。 相对运算符和目标可以由单个参数替代 - 一个谓词函数,如果(且仅当)其参数位于目标的大于一侧时返回true。
这给你“第一>”和“第一> =”。要获得“first ==”,请使用“first> =”,并在循环退出后添加相等性检查。
对于“last <”等,其原理与上述相同,但范围为 反映。这只是意味着你调整了边界调整(但不包括 评论)以及更改运算符。但在此之前,请考虑以下内容...
a > b == !(a <= b)
a >= b == !(a < b)
另外...
- 位置(最后的键<目标)=位置(第一密钥> =目标) - 1个
- 位置(最后的键< =目标)=位置(第一密钥>目标) - 1
当我们在搜索过程中移动我们的界限时,双方都会朝目标移动,直到他们相遇。还有刚刚低于下限特殊的项目,就像有只超过上限时...
while (upperbound > lowerbound)
{
testpos = lowerbound + ((upperbound-lowerbound)/2);
if (item[testpos] > goal)
{
// new best-so-far for first key > goal at [upperbound]
upperbound = testpos;
}
else
{
// new best-so-far for last key <= goal at [lowerbound - 1]
lowerbound = testpos + 1;
}
}
因此,在某种程度上,我们同时运行两个互补的搜索。当上限和下限相遇时,我们在该单一边界的每一边都有一个有用的搜索结果。
对于所有情况,原始“想象”出界 最佳到此位置是您的最终结果(在搜索范围内没有匹配)。在做最后的==
检查 第一个==和最后一个==情况之前,需要检查它。这也可能是有用的行为 - 例如如果 您正在寻找插入目标项目的位置,则在现有项目的 末尾添加该位置是正确的,如果所有现有项目 都小于您的目标项目。
一对夫妇对testpos的选择笔记...
testpos = lowerbound + ((upperbound-lowerbound)/2);
首先,这将不会溢出,不像比较明显((lowerbound + upperbound)/2)
。它也适用于指针以及整数 索引。
其次,该部门被假定为圆形。下降非负数 是确定的(所有你可以肯定在C),因为差异始终是非负数 无论如何。
这是一个方面,如果您使用非半开放式 范围,可能需要谨慎,但是 - 确保测试位置在搜索范围内,而不仅仅是在外面(在已经找到的最佳范围之一上)目前为止的职位)。
最后,在二叉树搜索范围的移动是隐性和 选择testpos
内置于树的结构(可能是 不平衡),但同样的原则也适用于搜索是什么这样做。在这个 的情况下,我们选择我们的子节点缩小隐式范围。对于第一场比赛 的情况,要么我们找到了一个新的较小的最佳匹配(找到较小的孩子,希望找到一个更小,更好的孩子),或者我们已经超过(希望恢复的更高的孩子)。再次,可以通过切换比较运算符来处理四种主要情况。
顺便说一句 - 有更多可能的操作符用于该模板参数。考虑按年份和月份排序的数组。也许你想找到特定年份的第一个项目。要做到这一点,编写一个比较年份并忽略月份的比较函数 - 如果年份相同,则目标相当于平等,但目标值可能与密钥的类型不同,该密钥甚至没有月份值比较。我认为这是一个“部分关键比较”,并将其插入到二进制搜索模板中,并获得我认为的“部分关键搜索”。
编辑以下段落用于说“1999年12月31日等于2000年2月1日”。除非整个范围也被认为是平等的,否则这是行不通的。重点是开始和结束日期日期的所有三个部分都不相同,因此您不需要处理“部分”键,但被认为等同于搜索的键必须在容器中形成一个连续的块,这通常意味着在可能的键的有序集合中连续的块。
它不完全是“部分”键。您的自定义比较可能会考虑到1999年12月31日等于2000年1月1日,但所有其他日期不同。关键在于自定义比较必须与排序的原始关键字一致,但考虑到所有不同的值可能不会太挑剔 - 它可以将一系列关键字视为“等价类”。
关于我真的应该包括前界一个额外的音符,但也许我还没有想过这个问题在当时这种方式。
思考边界的一种方法是它们根本不是项目。一个绑定的两个项目之间的边界线,所以你可以很容易地为编号的边界线,你可以编号的项目...
| | | | | | | | |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| |0| | |1| | |2| | |3| | |4| | |5| | |6| | |7| |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| | | | | | | | |
0 1 2 3 4 5 6 7 8
明显界限的编号与项目的编号。只要你从左到右编号,并用相同的方式为你的项目编号(在这种情况下从零开始),结果与常见的半开公约相同。
将有可能选择一个中间范围将范围精确地平分为两个,但这不是二进制搜索的功能。对于二进制搜索,您可以选择一个要测试的项目 - 不是绑定。该项目将在此迭代中进行测试,不得再次测试,因此它将从两个子范围中排除。
| | | | | | | | |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| |0| | |1| | |2| | |3| | |4| | |5| | |6| | |7| |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| | | | | | | | |
0 1 2 3 4 5 6 7 8
^
|<-------------------|------------->|
|
|<--------------->| | |<--------->|
low range i hi range
所以在算法testpos
和testpos+1
是翻译项目索引,约束指数的两种情况。当然,如果两个边界相等,则该范围内没有项目可供选择,因此循环无法继续,唯一可能的结果就是该边界值。
上面显示的范围只是仍然需要搜索的范围 - 我们打算在久经考验的和已证明的更高范围之间关闭的差距。
在此模型中,二分查找搜索两个有序种类值之间的边界 - 分类为“较低”和归类为“较高”的那些边界。谓词测试将一个项目分类。没有“平等”等级 - 等于键值是较高等级(对于x[i] >= key
)或较低等级(对于x[i] > key
)的一部分。
- 1. 二进制搜索和eps比较
- 2. 二进制比较
- 3. 二进制搜索的比较次数的复发关系
- 4. 如何使用二进制搜索比较x509certificates
- 5. 二进制数比较
- 6. 二进制搜索优化的比较数量方面
- 7. 我可以比二进制搜索做得更好吗?
- 8. 比较两个二进制文件
- 9. 图灵机比较二进制
- 10. 比较二进制整数ruby
- 11. 构建二叉搜索树时的比较次数
- 12. 什么比较方法比较好?
- 13. 比较两次
- 14. MySQL比较二进制排序与二进制字符串
- 15. iOS - 如何比较两次?
- 16. 如何计算比较器/可比较器中的比较次数?
- 17. 如何分割一串二进制数来比较每个数字?
- 18. 如何在Python中进行安全的二进制比较?
- 19. 理解的NSString比较
- 20. 8元素二进制堆需要多少次比较?
- 21. iOS比较地理位置
- 22. 无法理解可比性,比较
- 23. 哪一个比较好?
- 24. 二进制字符串比较/分类与字典字符串比较/排序
- 25. 比较地点
- 26. 如何比较IP地址
- 27. 比较比较Int
- 28. 如何比较有理数?
- 29. iPhone - 比较两次
- 30. iphone - 比较两次
+1写得很好的解释,先生。 – WhozCraig 2013-02-19 05:14:55