为啥中位数算法被描述为使用 O(1) 辅助空间?
Posted
技术标签:
【中文标题】为啥中位数算法被描述为使用 O(1) 辅助空间?【英文标题】:Why is the median-of-medians algorithm described as using O(1) auxiliary space?为什么中位数算法被描述为使用 O(1) 辅助空间? 【发布时间】:2016-04-06 08:38:33 【问题描述】:Wikipedia lists the median-of-medians algorithm as requiring O(1)
auxiliary space.
但是,在算法的中间,我们对大小为n/5
的子数组进行递归调用以找到中位数的中位数。当这个递归调用返回时,我们使用返回的中位数作为基准对数组进行分区。
这个算法不是将O(lg n)
激活记录作为递归的一部分推送到运行时堆栈吗?据我所知,这些寻找中位数的连续中位数的递归调用不能进行尾调用优化,因为我们在递归调用返回后做了额外的工作。因此,该算法似乎需要O(lg n)
辅助空间(就像快速排序一样,由于运行时堆栈使用的空间,***将其列为需要O(lg n)
辅助空间)。
是我遗漏了什么,还是***的文章有误?
(注意:我指的递归调用是***页面上的return select(list, left, left + ceil((right - left) / 5) - 1, left + (right - left)/10)
。)
【问题讨论】:
@Nuclearman 这是一个公平的观点,但是pivot
函数会调用select,
,所以我们不能忽略select
所需的空间。 Wikipedia 文章将这两个函数描述为相互递归。如果我们忽略对select
的调用,我们最终不会得到中位数的中位数。相反,我们最终得到n/5
中位数为 5。
@Nuclearman Quickselect 不需要堆栈,因为它可以进行尾调用优化。它可以进行尾调用优化,因为我们在递归中基本上只遍历一个从根到叶的路径。我们不需要记住我们的历史。 Median-of-medians 不能以相同的方式优化,因为它在每个级别都有多个递归调用。换句话说,我们在算法执行期间遍历了多个从根到叶的路径。我们需要记住我们的历史,这样当我们到达一片叶子时,我们就知道该往哪里回去。我们不能在恒定空间中进行这样的遍历。
@John 我目前也在处理这个问题,并且同意你的所有观点。我 asked the author ***的 O(1) 声明,希望我们能得到答案。
@StefanPochmann 谢谢斯特凡。我首先根据 Leetcode 的 Wiggle Sort II 开始对此进行调查,您似乎也在查看 :) 感谢您与原作者联系。不知道这是一个选择!
@John 是的,leetcode 也是如此。我几乎可以肯定这不是巧合。但巧合的是,您当前的 3137 点是 1337 的字谜 :-)
【参考方案1】:
虽然我不能排除 O(1) 是可能的,但***的信息似乎是一个错误。
那里显示的实现需要 O(log n) 而不仅仅是 O(1)。 如何用 O(1) 实现它绝对不明显,也没有任何解释/参考。 我问过originally added that information 和he replied 的作者,他不记得了,这可能是一个错误。 一个paper from 2005 致力于解决 O(n) 时间和 O(1) 额外空间的选择问题,他说 BFPRT(又名 Median of Medians)使用 Θ(log n) 额外空间。另一方面,论文的主要结果是 O(n) 时间和 O(1) 额外空间是可能的,并且作为证明提出的两种算法之一是 BFPRT 的一些“仿真”。所以从这个意义上说是可能的,但我认为仿真让它成为一种不同的算法,并且 O(1) 不应该归因于“常规”BFPRT。至少不是没有解释。 (感谢 Yu-HanLyu 在 cmets 中展示该论文及更多内容)【讨论】:
最后一点假设输入是只读的。如果输入是可修改的,则可以就地进行中位数查找。 @Yu-HanLyu 你确定吗?该论文的介绍说有两种类型的恒定工作空间算法,一种允许修改输入。它列出了就地堆排序和快速排序作为示例。也就是说,我想如果我们排除一些输入数字,我们确实可以使中位数算法只占用 O(1) 额外空间,用最后的数字替换它们,然后滥用该端来存储递归信息。这也是您的想法,还是有一种方法可以至少重新排列输入而不会丢失数据? 我不知道中位数算法是否可以就地完成。但是,我相信有一些就地选择算法。见link.springer.com/chapter/10.1007%2FBFb0015429cai.sk/ojs/index.php/cai/article/viewArticle/345和link.springer.com/chapter/10.1007%2F3-540-19487-8_2我猜最后一个接近你的想法,本文还提到中位数算法使用 O(log n) 额外空间。 但是 in-place 版本可能比原始版本更慢,例如快速排序的无堆栈版本比原始快速排序慢。 @Yu-HanLyu 抱歉,想先看看这些论文。但我现在删除了最后一点,明天将添加一个替代品(现在太累了,无法制定它)。【参考方案2】:是 O(1)。
假设我们从一个长度为 n 的数组开始,我们打算找到排序列表的第 k 个元素。
在第一次调用中位数之后会吐出一个较小的数组,现在我们需要对这个较小数组的第i个元素求值。请注意,这个较小数组的第 i 个元素是结果,因此我不需要将任何信息传递回之前的调用。
在快速排序中,我需要将已排序的小数组放回正确的位置,因此会发生递归。中位数的中位数,在循环(尾递归)之后,我将得到答案。
递归深度 = O(1)
【讨论】:
找到对数组进行分区的支点需要 O(lg n) 内存。在 1) 找到枢轴和 2) 围绕该枢轴对数组进行分区之前,您无法拆分出较小的数组。你是对的,一旦你对数组进行了分区,你就不需要将任何信息传递回原来的调用者,但是你忽略了一个事实,即找到枢轴需要 O(lg n) 内存。以上是关于为啥中位数算法被描述为使用 O(1) 辅助空间?的主要内容,如果未能解决你的问题,请参考以下文章