为啥二分搜索是一种分而治之的算法?

Posted

技术标签:

【中文标题】为啥二分搜索是一种分而治之的算法?【英文标题】:Why is Binary Search a divide and conquer algorithm?为什么二分搜索是一种分而治之的算法? 【发布时间】:2012-02-09 15:34:48 【问题描述】:

有人问我,在考试中,二分搜索是否是一种分而治之的算法。我的回答是肯定的,因为你把问题分成更小的子问题,直到你得到你的结果。

但考官问其中的征服部分在哪里,我无法回答。他们也不赞成它实际上是一种分而治之的算法。

但是我在网上到处都是,它说它是,所以我想知道为什么,它的征服部分在哪里?

【问题讨论】:

征服部分是你解决问题的地方。我不确定他们为什么说这不是分而治之。这些是你应该确保也问你的考官的事情!他们是唯一知道他们正在寻找的答案的人。 好吧,你只搜索一半。所以,是的,你划分问题。但是不,你不会征服两半,只有一个(至少当你很聪明的时候。;-)) 是减少和征服吗?这是***的引述。 “已提议将名称减少和征服改为单子问题类”en.wikipedia.org/wiki/… @CodyGray 我知道我迟到了,但请参阅my answer,了解为什么二分搜索并不是真正的 DnC 算法。它比公认的答案更详细地说明了任何算法需要具备的属性才能被视为 DnC 算法。 【参考方案1】:

Data Structures and Algorithm Analysis in Java, 2nd edtition, Mark Allen Weiss

说 D&C 算法应该有两个不相交的递归调用。即喜欢快速排序。 Binary Search没有这个,即使它可以递归实现,所以我猜这就是答案。

【讨论】:

This answer 还添加了有关 DnC 算法的常见属性的更多详细信息,而二进制搜索实际上并不具备这些属性。【参考方案2】:

我认为不是分而治之,见http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm第一段

递归地将问题分解为两个或多个子问题 然后将它们组合起来给出一个解决方案

在二分查找中,仍然只有一个问题,就是每一步将数据减半,因此不需要对结果进行征服(合并)阶段。

【讨论】:

“Conquer == merge”过于简单化了,但仍然:“将数据减少一半”可以是例如被视为“将这一半(一组 0 个结果)与另一半合并”。 有了这个定义,你仍然可以争论:你“划分”成两个子问题,其中一个绝对不包含目标(因此无需进一步工作就可以轻松解决),另一个其中可能。然后“结合”它不在一个中的事实,以及它是否在另一个中的答案。对我来说,这表明将其变成考试主题是徒劳的。谁认为二分搜索可以被标记为“分而治之”并不重要,谁认为不应该这样做并不重要,因为没有 D&C 的正式定义,也没有结果适用于如果算法是 D&C。 ...尽管再三考虑,我想如果您定义“征服”必须使用两个非平凡的解决方案进行非平凡的工作,那么定义上的 D&C 算法并不是单独的-递归而不是尾递归。这些都是重要的属性,我认为这意味着例如 D&C 算法使用超过 O(1) 的额外空间。 阅读这个:csc.liv.ac.uk/~ped/teachadmin/algor/d_and_c.html 我和你一样争论:“你必须解决每个子问题”。但是错了..合并阶段是将两个子问题合二为一,结果就是找到的元素。 同意你的观点,但我仍然宁愿将分而治之作为并行处理的技术(在二进制搜索情况下不可用),但无论如何讨论都很酷。【参考方案3】:

分而治之:

1.问题分为几部分;

2.通过应用手头的算法(主要是递归用于此目的),这些部分中的每一个都被独立攻击/解决;

3.然后将每个分区/划分的解决方案组合/合并在一起,得出整体问题的最终解决方案(这属于征服

示例,快速排序,合并排序。

基本上,二分搜索算法只是在每次迭代中将其工作空间(大小为 n 的输入(有序)数组)分成两半。因此它肯定是在部署 divide 策略,结果,时间复杂度降低到 O(lg n)。所以,这掩盖了它的“divide”部分。

可以注意到,最终的解决方案是从最后一次比较中获得的,也就是说,当我们只剩下一个元素进行比较时。 二分搜索不合并或组合解决方案。

简而言之,二分搜索将问题(它必须解决的问题)的大小分成两半,但找不到零碎的解决方案,并且因此不需要合并解决方案!

我知道这有点太长了,但我希望它会有所帮助:)

您也可以从https://www.khanacademy.org/computing/computer-science/algorithms/binary-search/a/running-time-of-binary-search获得一些想法

我刚刚意识到这个问题很久以前就发布了! 我的错!

【讨论】:

【参考方案4】:

不是。

作为对@Kenci's post 的补充,DnC 算法具有一些通用/通用属性;他们:

    将原始问题实例划分为其自身的一组较小的子实例; 独立解决每个子实例; 结合较小/独立的子实例解决方案,为较大/原始实例构建单一解决方案

二分搜索的问题在于它确实真的按照步骤1生成一组要解决的独立子实例;它只是通过永久丢弃它不感兴趣的部分来简化原始问题。换句话说,它只是减少了问题的大小,而且一直到现在为止。 p>

DnC 算法不仅应该相互独立地识别/解决原始问题的较小子实例,而且还应该使用该组部分独立解决方案来“构建”较大问题实例的单个解决方案作为一个整体。

Fundamentals of Algorithmics,G. Brassard,P. Bratley 这本书说如下(我的重点是粗体,原文为斜体):

这可能是分治法最简单的应用了,其实简单到严格来说这是一个简化的应用,而不是分治法 : 任何足够大的实例的解决方案都会减少到单个较小实例的解决方案,在这种情况下是一半大小。

p.226 上的

7.3 二进制搜索部分。

【讨论】:

【参考方案5】:

显然,有些人认为二进制搜索是一种分而治之的算法,而有些人则不是。我很快搜索了三个参考文献(似乎都与学术界有关),它们称之为 D&C 算法: http://www.cs.berkeley.edu/~vazirani/algorithms/chap2.pdf http://homepages.ius.edu/rwisman/C455/html/notes/Chapter2/DivConq.htm http://www.csc.liv.ac.uk/~ped/teachadmin/algor/d_and_c.html

我认为 D&C 算法应该至少具有这三个阶段的前两个阶段是普遍的共识:

划分,即决定如何将整个问题分成子问题; 征服,即独立解决每个子问题; [可选地]合并,即将独立计算的结果合并在一起。

第二阶段 - 征服 - 应该递归地应用相同的技术来解决子问题,方法是分成更小的子子问题等等。然而,在实践中,通常使用一些阈值来限制递归方法,如对于小尺寸问题,不同的方法可能会更快。例如,快速排序实现经常使用例如当要排序的数组部分的大小变小时时,冒泡排序。

第三阶段可能是空操作,在我看来,它不会取消算法作为 D&C 的资格。一个常见的例子是for-loop 的递归分解,所有迭代都纯粹使用独立的数据项(即不减少任何形式)。乍一看它可能看起来毫无用处,但实际上它是非常强大的方式,例如并行执行循环,并被 Cilk 和 Intel 的 TBB 等框架使用。

回到最初的问题:让我们考虑一些实现算法的代码(我使用 C++;如果这不是您喜欢的语言,请见谅):

int search( int value, int* a, int begin, int end ) 
  // end is one past the last element, i.e. [begin, end) is a half-open interval.
  if (begin < end)
  
    int m = (begin+end)/2;
    if (value==a[m])
      return m;
    else if (value<a[m])
      return search(value, a, begin, m);
    else
      return search(value, a, m+1, end);
  
  else // begin>=end, i.e. no valid array to search
    return -1;

这里的分割部分是int m = (begin+end)/2;,其余的都是征服部分。该算法以递归 D&C 形式显式编写,即使只采用其中一个分支。不过也可以写成循环形式:

int search( int value, int* a, int size ) 
  int begin=0, end=size;
  while( begin<end ) 
    int m = (begin+end)/2;
    if (value==a[m])
      return m;
    else if (value<a[m])
      end = m;
    else
      begin = m+1;
  
  return -1;

我认为用循环实现二分查找是很常见的方法;我特意使用了与递归示例中相同的变量名,以便更容易看出共性。因此我们可以再次说,计算中点是除法部分,循环体的其余部分是征服部分。

当然,如果你的考官有不同的想法,可能很难让他们相信这是 D&C。

更新:刚刚想到如果我要开发一个 D&C 算法的通用骨架实现,我肯定会使用二进制搜索作为 API 适用性测试之一,以检查 API 是否足够强大同时又简洁。当然这并不能证明什么:)

【讨论】:

你如何看待 Weiss 所说的传统 D&C 算法需要两个不相交的递归? 好吧,由于 D&C 算法的概念相当不正式(至少我不知道有一个普遍接受的正式定义),因此有解释的空间,因此 Weiss 有权以这种方式解释。 我会说,征服而不看另一半——因为你知道答案不可能存在——是征服的终极目标。 "D&C" 是非正式的,以至于我认为您甚至不能说它必然包含“划分”和“征服”两个步骤。 “征服”不是一些行话,意思是“解决”,它与“分割”结合起来给出了这个表达。事实上,“分而治之”是来自拉丁格言Divide et impera的英语成语。更直译是“分而治之”,也是惯用的英语。成语的意思是“我可以通过划分问题(或受害者)来获胜”,而不是“首先我将执行'划分'步骤,然后执行'征服'步骤,这样我就赢了”。跨度> 【参考方案6】:

二分搜索很难用分治法来描述,因为征服步骤并不明确。算法的结果是大海捞针的索引,纯D&C实现会返回大海捞针在最小大海捞针的索引(单元素列表中的0),然后递归添加偏移量在除法步骤中划分的较大干草堆。

伪代码解释:

function binary_search has arguments needle and haystack and returns index
    if haystack has size 1
       return 0
    else 
        divide haystack into upper and lower half
        if needle is smaller than smallest element of upper half
            return 0 + binary_search needle, lower half
        else
            return size of lower half + binary_search needle, upper half

添加(0 +size of lower half)是征服部分。大多数人通过在更大的列表中提供索引作为参数来跳过它,因此它通常不容易获得。

【讨论】:

【参考方案7】:

分割部分当然是将集合分成两半。

征服部分是确定在处理部分中是否以及在哪个位置有搜索到的元素。

【讨论】:

我不同意。这是一个仅除法算法。一半根本没有处理。【参考方案8】:

计算机科学中的二分法是指在两个对立的选择之间进行选择,在两个截然不同的选择之间进行选择。二分法是将一个整体分成两个不重叠的部分,这意味着它是将一个整体分为两个部分的过程。它是将一个整体(或一组)划分为两个部分(子集),它们是: 1. 共同穷尽:一切都必须属于一个部分或另一个部分,并且 2. 互斥:没有任何东西可以同时属于这两个部分。

分治法通过递归地将一个问题分解为两个或多个相同类型的子问题,直到这些子问题变得简单到可以直接解决。

因此,二分搜索将每次迭代检查的项目数量减半,并确定它是否有机会在该一半中找到“关键”项目,或者如果它能够确定关键不存在,则继续移动到另一半。由于该算法本质上是二分法的,因此二分搜索将认为“密钥”必须在一个部分中,直到它到达退出条件,返回密钥丢失。

【讨论】:

【参考方案9】:

分治算法基于以下3个步骤:

    除法 征服 结合

二分查找问题可以定义为在有序数组 A[n] 中找到 x。 根据此信息:

    除法:比较 x 和中间 征服:在一个子数组中递归。 (在这个数组中找到 x) 结合:没有必要。

【讨论】:

【参考方案10】:

Merge SortQuick Sort 算法使用 分而治之 技术(因为有 2 个子问题),Binary Search 属于 减少和征服 (因为有 1 个子问题)。

因此,二分搜索实际上使用的是递减和征服技术,而不是分而治之技术。

来源:https://www.geeksforgeeks.org/decrease-and-conquer/

【讨论】:

【参考方案11】:

适当的分而治之的算法将需要处理这两个部分。

因此,很多人不会将二分搜索称为分治算法,它确实了问题,但丢弃了另一半。

但很可能,你的考官只是想看看你的论点。 (好的)考试不是关于事实,而是关于当挑战超出原始材料时你的反应。

所以恕我直言,正确的答案是:

嗯,从技术上讲,它只包含一个分步,但只需要完成原任务的一半,另一半已经完成。

顺便说一句:QuickSort 有一个很好的变体,称为 QuickSelect,它实际上利用这种差异来获得平均 O(n) 中值搜索算法。它类似于 QuickSort - 但只下降到它感兴趣的一半。

【讨论】:

【参考方案12】:

非正式的定义或多或少:将问题分解为小问题。然后解决它们并将它们放在一起(征服)。解决实际上是决定下一步去哪里(左,右,找到元素)。

这里引用wikipedia:

“分而治之”这个名称有时也适用于将每个问题简化为仅一个子问题的算法,例如用于在排序列表中查找记录的二分查找算法。

这表明,它不是 [更新:误读了这句话:)] 只是分而治之的一部分。

更新: This 文章让我明白了。我很困惑,因为定义说你必须解决每个子问题。但是,如果您知道不必继续搜索,您就解决了子问题..

【讨论】:

en.wikipedia.org/wiki/Binary_search_algorithm 。它说:二分搜索是一种二分法分而治之的搜索算法。 另外,QuickSort 可以在原地工作,因此它实际上并没有返回并将所有谜题放在一起。 @Kenci:***在那里自相矛盾。 “二进制搜索算法”页面说它是 D&C,而您的报价链接到的 D&C 页面说,根据定义,D&C 涉及 multiple 递归,而二进制搜索不涉及。这就是你使用数千个不一致的编辑器的结果,你的考官不会同意所有的;-) @SteveJessop 但它可以被实现为递归搜索比较指定的子范围? @Kenci:当然,但它只会是单递归的。在绝对不包含目标的一半上递归也没有意义。我假设你的考官会遵循 D&C 页面上的定义,或者类似的东西,如果算法不需要“多分支”递归,那么它就不是 D&C。【参考方案13】:

二分搜索是一种分而治之的算法:

1) 在分而治之的算法中,我们尝试通过解决较小的子问题(Divide part)来解决问题,并使用该解决方案来构建我们更大的问题(Conquer)的解决方案。

2) 这里我们的问题是在排序后的数组中找到一个元素。我们可以通过解决类似的子问题来解决这个问题。 (我们在这里创建子问题是基于正在搜索的元素小于或大于中间元素的决定)。因此,一旦我们知道元素不能肯定存在于另一半,我们就在另一半解决类似的子问题。

3) 这样我们就递归了。

4)这里的征服部分只是将子问题返回的值返回到递归树的顶部

【讨论】:

【参考方案14】:

我认为这是减少和征服。

这是来自***的引述。

“名称减少和征服已被提议代替 单子问题类”

http://en.wikipedia.org/wiki/Divide_and_conquer_algorithms#Decrease_and_conquer

根据我的理解,“征服”部分是在找到二分搜索的目标元素的最后。 “减少”部分是减少搜索空间。

【讨论】:

【参考方案15】:

二分查找和三分查找算法基于减少和征服技术。因为,您不划分问题,实际上通过除以 2(在三元搜索中为 3)来减少问题。

合并排序和快速排序算法可以作为分治技术的例子。您将问题分成两个子问题,并再次使用这些子问题的算法对数组进行排序。但是,您在二进制搜索中丢弃了数组的一半。这意味着您减小数组的大小,而不是除以。

【讨论】:

【参考方案16】:

不,二分查找不是分而治之。是的,二分搜索是减少和征服。我相信分而治之算法的效率为 O(n log(n)),而减少和征服算法的效率为 O(log(n))。区别在于您是否需要评估数据拆分的两个部分。

【讨论】:

我已编辑您的帖子以删除一些闲聊文字。如果您仍想分享,您可以用我删除的内容评论您自己的答案。谢谢!【参考方案17】:

二分搜索是一种减少和征服算法,而不是分而治之

二分搜索是一种减少和征服算法,其中子问题的大小大约是原始大小的一半,具有悠久的历史。另一种古老的减少和征服算法是 欧几里得算法,用于计算最大公约数通过将数字减少为越来越小的等效子问题,这可以追溯到公元前几个世纪。

分而治之是解决概念上难题的强大工具:它所需要的只是一种将问题分解为子问题、解决琐碎案例并将子问题与原始问题结合起来的方法。类似地,减少和征服只需要将问题简化为一个较小的问题,例如经典的汉诺塔谜题,它将移动高度为 n 的塔简化为移动高度为 n-1 的塔。

参考:link 1 和 link 2!

【讨论】:

以上是关于为啥二分搜索是一种分而治之的算法?的主要内容,如果未能解决你的问题,请参考以下文章

分而治之的算法(二分搜索的应用?!)

你能写出满分的二分查找算法吗

算法复习_分治算法之二分搜索棋盘覆盖快速排序

递归迭代和分治:分治与二分查找

python 二分法查找

二分搜索算法