pol*_*nts 65 arrays sorting algorithm
我最近被问到这个面试问题:
您将获得一个几乎已排序的数组,因为每个
N
元素可能被错放的k
位置不超过正确排序顺序的位置.找到一种节省空间和时间的算法来对数组进行排序.
我有一个O(N log k)
解决方案如下.
让我们表示arr[0..n)
从索引0
(包括)到N
(不包括)的数组元素.
arr[0..2k)
arr[0..k)
它们处于最终的排序位置......arr[k..2k)
可能仍然被错放了k
!arr[k..3k)
arr[k..2k)
它们处于最终的排序位置......arr[2k..3k)
可能仍然被错放了k
arr[2k..4k)
arr[ik..N)
,然后你就完成了!
2k
剩下的元素少于其他元素,那么最后一步可能比其他步骤便宜在每个步骤中,您对大多数2k
元素进行排序,在每个步骤O(k log k)
结束时将至少k
元素放在最终排序位置.有O(N/k)
步骤,所以整体复杂性O(N log k)
.
我的问题是:
O(N log k)
最佳的?这可以改进吗?Nor*_*sey 36
正如Bob Sedgewick在他的论文工作(和后续工作)中所表明的那样,插入排序绝对会破坏 "几乎排序的数组".在这种情况下,你的渐近线看起来不错,但如果k <12,我认为插入排序每次都会获胜.我不知道为什么插入排序这么好有一个很好的解释,但是看看的地方将是Sedgewick的一本名为算法的教科书(他为不同的语言做了很多版本).
我不知道O(N log k)是否是最优的,但更重要的是,我并不在乎 - 如果k很小,它是重要的常数因子,如果k很大,你可能也只是对数组进行排序.
插入排序将解决此问题,而无需重新排序相同的元素.
Big-O表示法对于算法类来说非常好,但在现实世界中,常量很重要.很容易忽视这一点.(我说这是一位教过Big-O符号的教授!)
小智 19
如果仅使用比较模型,则O(n log k)是最佳的.考虑k = n时的情况.
要回答你的另一个问题,是的,可以通过使用堆来进行排序而不进行排序.
使用2k元素的最小堆.首先插入2k元素,然后删除min,插入下一个元素等.
这保证了O(n log k)时间和O(k)空间,而堆通常具有足够小的隐藏常数.
已经指出,其中一个渐近最优解决方案使用最小堆,我只是想用Java提供代码:
public void sortNearlySorted(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int i = 0; i < k; i++) {
minHeap.add(nums[i]);
}
for (int i = 0; i < nums.length; i++) {
if (i + k < nums.length) {
minHeap.add(nums[i + k]);
}
nums[i] = minHeap.remove();
}
}
Run Code Online (Sandbox Code Playgroud)
由于k
显然应该是非常小的,插入排序可能是最明显和普遍接受的算法.
在随机元素的插入排序中,您必须扫描N个元素,并且必须将每个元素平均移动N/2个位置,从而总共运算~N*N/2.在大O(或类似)表征中忽略"/ 2"常数,给出O(N 2)复杂度.
在您提议的情况下,预期的操作次数为~N*K/2 - 但由于k
是常数,因此k/2
在大O特征中忽略整个项,因此总体复杂度为O(N).
如果k
足够大,你的解决方案是好的.在时间复杂性方面没有更好的解决方案; 每个元素可能都k
不合适,这意味着您需要学习log2 k
一些信息才能正确放置,这意味着您log2 k
至少需要进行比较 - 所以它至少应该是复杂的O(N log k)
.
然而,正如其他人所指出的那样,如果k
规模很小,那么常数条款就会杀了你.在这种情况下,使用每次操作非常快的东西,比如插入排序.
如果你真的想要达到最佳状态,你可以实现两种方法,并根据它们从一种方法切换到另一种方法k
.