【问题标题】:Why use binary search if there's ternary search?如果有三元搜索,为什么要使用二分搜索?
【发布时间】:2015-10-29 09:22:34
【问题描述】:

我最近听说过三元搜索,我们将一个数组分成 3 部分并进行比较。这里将进行两次比较,但它将数组减少到 n/3。为什么人们不使用这么多?

【问题讨论】:

  • 如果数组只有两个元素怎么办?
  • 这是个特例
  • 令人惊讶的是,所有答案都只谈论时间复杂度。在许多情况下,空间复杂性同样重要,三叉树通常更节省空间。 (而且,鉴于现代 CPU 架构,空间复杂度通常会对实际性能产生重大影响)。
  • 我很感激这是一个 6 年前的问题。您描述的不是三元搜索,有人可以编辑标题吗?仅供参考,三元搜索用于单峰函数,二元搜索用于单调函数。

标签: algorithm data-structures binary-tree ternary-tree


【解决方案1】:

实际上,人们确实使用 k-ary 树来表示任意 k。

然而,这是一个权衡。

要在 k-ary 树中查找元素,您需要大约 k*ln(N)/ln(k) 操作(请记住更改基数公式)。您的 k 越大,您需要的整体操作就越多。

您所说的逻辑扩展是“为什么人们不对 N 个数据元素使用 N 叉树?”。当然,这将是一个数组。

【讨论】:

  • B-trees 是介于数组和二叉树之间的 k-ary 树,常用;大于 3 阶的 k-ary 树肯定是有目的的。
  • 顺便说一句,k/ln(k)k=e 处最小(即k=2.71),因此“最佳”k-ary 树是 e-ary。二进制非常接近。
  • @Cucu 但三元不是更接近吗?
  • 更近了。但由于差异不大,实现的简单性变得更加重要,因此二进制获胜。
【解决方案2】:

三元搜索仍会为您提供相同的渐近复杂度 O(log N) 搜索时间,并增加实现的复杂性。

同样的论点可以解释为什么您不想要四边形搜索或任何其他更高阶。

【讨论】:

  • @Nikita:Log2 N = Log3 N / Log3 2 = 常数 * Log3 N = O(Log N)。任何两个复杂度的对数阶之间只有一个常数因子差异。
【解决方案3】:

搜索 10 亿(10 亿美元 - 1,000,000,000)个排序项与二分搜索相比平均需要大约 15 次比较,而与三元搜索相比大约需要 9 次 - 这不是一个巨大的优势。请注意,每个“三元比较”可能涉及 2 次实际比较。

【讨论】:

  • 事实上,“k-ary compare”包含在“k”键比较中,这可能是回答这个问题时要提到的最重要的因素。 +1
  • 直到现在我才知道“十亿”有不同的含义。干杯。
  • @Akusete:这里有 10 亿,那里有 10 亿……很快你就会得到一些大数字。
  • @MichaelBurr:恐怕在 10 亿条记录的数组中进行二进制搜索平均需要大约 30 次比较,而不是 15 次 ;-)
【解决方案4】:

哇。我认为,票数最高的答案错过了这一点。

您的 CPU 不支持将三元逻辑作为单个操作;它将三元逻辑分解为二进制逻辑的几个步骤。 CPU 的最佳代码是二进制逻辑。如果芯片普遍支持三元逻辑作为单一操作,那你就对了。

B-Trees 在每个节点上可以有多个分支; 3 阶 B 树是三元逻辑。树的每一步都会进行两次比较而不是一次,这可能会导致它在 CPU 时间上变慢。

然而,B-Trees 很常见。如果您假设树中的每个节点都将单独存储在磁盘上的某个位置,那么您将花费大部分时间从磁盘读取...... CPU 不会成为瓶颈,但磁盘会。因此,您采用每个节点有 100,000 个子节点的 B 树,或者任何其他几乎适合一块内存的东西。具有这种分支因子的 B 树的高度很少会超过三个节点,并且您只需进行 3 次磁盘读取(在瓶颈处停留 3 次)即可搜索庞大的数据集。

审核:

  • 硬件不支持三叉树,因此它们的运行速度较慢。
  • 阶数远高于 3 的 B-tress 常用于大型数据集的磁盘优化;超过 2 后,再超过 3。

【讨论】:

  • 三叉树有三个jmp操作(如果少则向左JB,如果更多则向右JA,如果相等则向下JE)。二叉树有两个二元操作(如果少则向左JB,如果多则向右JA)。处理器是否具有二进制或三元架构与它无关。只有指令 JAJBJE 在 IA-32 架构上。
【解决方案5】:

三元搜索比二分搜索更快的唯一方法是,如果可以以不到大约 1.55 倍的 2 路比较成本来确定 3 路分区。如果项目存储在排序数组中,则 3 路确定的平均成本将是 2 路确定的 1.66 倍。但是,如果信息存储在树中,则获取信息的成本相对于实际比较的成本较高,并且缓存局部性意味着随机获取一对相关数据的成本并不比获取单个数据的成本差多少datum,三叉树或n路树可以大大提高效率。

【讨论】:

    【解决方案6】:

    是什么让您认为三元搜索应该更快?

    平均比较次数:

    in ternary search = ((1/3)*1 + (2/3)*2) * ln(n)/ln(3) ~ 1.517*ln(n)
    in binary search  =                   1 * ln(n)/ln(2) ~ 1.443*ln(n).
    

    最差的比较次数:

    in ternary search = 2 * ln(n)/ln(3) ~ 1.820*ln(n)
    in binary search  = 1 * ln(n)/ln(2) ~ 1.443*ln(n).
    

    所以看起来三元搜索更糟糕。

    【讨论】:

    • @Moron:会是2 * log(n)/log3吗?我并不总是需要在一个级别上进行 2 次比较。例如——如果我有[..a..b..],而我确实x < a是真的,那么就没有必要和b比较了。
    • @Lazer:他确实说过“最坏的情况”。假设一棵平衡树和均匀分布的查找(摇晃假设),每个节点平均需要 1.5 次查找*。
    • 我不确定我们是否对最坏情况感兴趣,而是对平均需要多长时间感兴趣。
    • 你有这些数字的公开参考吗?
    • 这不可能:最坏的情况不是二分搜索的平均情况,因为如果您在之前的迭代中没有找到它,您只会进入最后一次迭代。有一半的时间,你无法完成最后一次迭代,所以这里肯定有问题。
    【解决方案7】:

    另外,请注意,如果我们继续,这个序列可以推广到线性搜索

    Binary search
    Ternary search
    ...
    ...
    n-ary search ≡ linear search
    

    因此,在 n 元搜索中,我们将有“one only COMPARE”,这可能需要进行 n 次实际比较。

    【讨论】:

    • 除了这两个极端之间有一些非常愉快的中间地带。看看我的回答吧?
    • @Dean J:是的,你是对的。如果有内置支持使得可以进行原子三元比较,那么三元搜索将是选择。但是在硬件级别本身实现三元逻辑有很多限制(它不受欢迎的一个原因)。我记得在某处读到e 在理论上是最经济的基础,尽管在硬件中实际做到这一点仍然是一个问题。因此,在我们拥有在硬件级别支持三进制逻辑的机器之前,二进制才是可行的方法。
    • 有趣的是,二分查找对于 CPU 周期来说是最优的,但如果树足够大以至于其中一些被分页到磁盘,则不是最优的;在这种情况下,具有 巨大 分支因子的 n 叉树实际上效果更好。
    【解决方案8】:

    “三元”(三元?)搜索在最好的情况下更有效,这将涉及搜索第一个元素(或者可能是最后一个,取决于您首先进行的比较)。对于远离末尾的元素,您首先要检查,虽然两次比较每次都会将数组缩小 2/3,但同样的两次二分搜索比较会将搜索空间缩小 3/4。

    除此之外,二分查找更简单。您只需比较并获得一半或其他一半,而不是比较,如果小于获得前三分之一,否则比较,如果小于获得第二个三分之一,则获得最后三分之一。

    【讨论】:

      【解决方案9】:

      三元搜索可以有效地用于并行架构 - FPGA 和 ASIC。例如,如果搜索所需的内部 FPGA 内存小于 FPGA 资源的一半,您可以制作一个重复的内存块。这将允许同时访问两个不同的内存地址并在单个时钟周期内进行所有比较。这就是为什么 100MHz FPGA 有时可以胜过 4GHz CPU 的原因之一 :)

      【讨论】:

        【解决方案10】:

        这里的some random experimental evidence that I haven't vetted at all 表明它比二分查找慢。

        【讨论】:

        • Permission denied :"此博客仅对受邀读者开放e-hon.blogspot.com 看起来您并没有被邀请阅读此博客。如果您认为这是一个错误,您可能想要联系博客作者并请求邀请。”
        【解决方案11】:

        几乎所有关于二叉搜索树的教科书和网站都没有真正谈论二叉树!他们向您展示了三元搜索树!真正的二叉树将数据存储在其叶子而不是内部节点中(导航键除外)。有人称这些叶子树,并区分教科书中显示的节点树:

        J。 Nievergelt,C.-K。 Wong:二叉树总路径长度的上限, 期刊 ACM 20 (1973) 1-6。

        以下内容来自 Peter Brass 关于数据结构的书。

        2.1 两种搜索树模型

        在刚刚给出的大纲中,我们隐藏了一个起初看起来很重要的点 微不足道,但实际上它导致了两种不同的搜索树模型, 它可以与以下大部分材料结合使用,但其中之一 是非常可取的。

        如果我们将每个节点中的查询键与包含在 如果查询键较小,则节点并跟随左分支并且右分支 如果查询键更大,那么如果它们相等会发生什么?两种型号 搜索树如下:

        1. 如果查询键小于节点键,则取左分支;否则采取 右分支,直到你到达树的叶子。内部节点中的键 树的只用于比较;所有的物体都在叶子里。

        2. 如果查询键小于节点键,则取左分支;选择正确的分支 如果查询键大于节点键;并取包含的对象 如果它们相等,则在节点中。

        这个小问题有很多后果:

        { 在模型 1 中,底层树是二叉树,而在模型 2 中,每个 树节点实际上是一个具有特殊中间邻居的三元节点。

        { 在模型 1 中,每个内部节点都有一个左子树和一个右子树(每个都可能是 树的叶节点),而在模型 2 中,我们必须允许不完整 节点,其中左子树或右子树可能丢失,并且只有 比较对象和键保证存在。

        所以模型 1 的搜索树的结构比树的结构更规则 模型2;至少对于实施而言,这是一个明显的优势。

        { 在模型 1 中,遍历一个内部节点只需要一次比较, 而在模型 2 中,我们需要两个比较来检查这三个 可能性。

        确实,模型 1 和 2 中相同高度的树最多包含大约 相同数量的对象,但需要在模型中进行两倍多的比较 2 到达树的最深处。当然,在模型 2 中,还有 一些更早到达的对象;找到根中的对象 只有两次比较,但几乎所有对象都在最深处或附近 级别。

        定理。高度为 h 且模型 1 的树最多包含 2^h 个对象。 高度为 h 且模型 2 的树最多包含 2^h+1 - 1 个对象。

        这很容易看出,因为高度为 h 的树具有左右子树 a 每棵树的高度最多为 h - 1,在模型 2 中,在两者之间增加一个对象 他们。

        { 在模型 1 中,内部节点中的键仅用于比较,并且可能 重新出现在叶子中以识别物体。在模型 2 中,每个 key 和它的对象只出现一次。

        在模型 1 中甚至可能有用于比较的键 不属于任何对象,例如,如果对象已被删除。经过 从概念上分离这些比较和识别的功能,这 不足为奇,在后来的结构中,我们甚至可能需要定义人工 测试不对应任何对象,只是为了得到良好的搜索划分 空间。用于比较的所有键都必须是不同的,因为在模型中 1棵树,每个内部节点都有非空的左右子树。所以每个键 最多出现两次,一次作为比较键,一次作为识别键 叶子。

        模型 2 成为首选的教科书版本,因为在大多数教科书中 对象和它的键之间没有区别:键是对象。 然后在树结构中复制键变得不自然。但在 在所有实际应用中,key 和 object 之间的区别是相当重要的。 人们几乎从不希望只跟踪一组数字。号码 通常与一些进一步的信息相关联,这通常是很多 大于密钥本身。

        【讨论】:

          【解决方案12】:

          您可能听说过在涉及用秤称重的谜语中使用了三元搜索。那些秤可以返回 3 个答案:左边更轻,两者相同,或者左边更重。所以在三元搜索中,只需要 1 次比较。 但是,计算机使用布尔逻辑,它只有 2 个答案。要进行三元搜索,您实际上必须进行 2 次比较而不是 1 次。 我想在某些情况下,这仍然比之前的海报提到的更快,但是您可以看到三元搜索并不总是更好,而且在计算机上实现起来更加混乱和不自然。

          【讨论】:

            【解决方案13】:

            理论上,k/ln(k) 的最小值是在 e 处实现的,由于 3 比 2 更接近 e,因此需要较少的比较。你可以检查3/ln(3) = 2.73..2/ln(2) = 2.88.. 二进制搜索可以更快的原因是它的代码分支更少,并且在现代CPU上运行速度更快。

            【讨论】:

              【解决方案14】:

              我刚刚发布了一个关于三元搜索的blog,我已经展示了一些结果。我还在我的git repo 上提供了一些初始级别的实现,我完全同意每个人关于三元搜索的理论部分的看法,但为什么不试一试呢?根据实现,如果你有三年的编码经验,那部分就很容易了。 我发现如果你有庞大的数据集并且你需要多次搜索它,三元搜索是有优势的。 如果您认为使用三元搜索可以做得更好。

              【讨论】:

                【解决方案15】:

                虽然你在两个搜索树中得到相同的大 O 复杂度 (ln n),但区别在于常量。您必须在每个级别对三元搜索树进行更多比较。因此,对于 k 元搜索树,差异归结为 k/ln(k)。这在 e=2.7 处具有最小值,并且 k=2 提供了最佳结果。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-16
                  • 2017-08-26
                  • 2020-09-17
                  • 1970-01-01
                  相关资源
                  最近更新 更多