我知道这个问题不太具体。
我想要的只是有人告诉我如何将普通合并排序转换为就地合并排序(或具有恒定额外空间开销的合并排序)。
我(在网上)能找到的所有页面都写着“它太复杂”或“超出了本文的范围”。
唯一已知的就地合并(没有任何额外空间)的方法太复杂,无法简化为实际程序。 (取自此处)
即使它太复杂,如何就地进行合并排序的基本概念是什么?
I know the question is not too specific.
All I want is someone to tell me how to convert a normal merge sort into an in-place merge sort (or a merge sort with constant extra space overhead).
All I can find (on the net) is pages saying "it is too complex" or "out of scope of this text".
The only known ways to merge in-place (without any extra space) are too complex to be reduced to practical program. (taken from here)
Even if it is too complex, what is the basic concept of how to make the merge sort in-place?
发布评论
评论(10)
Knuth 将此作为练习(第 3 卷,5.2.5)。确实存在就地合并排序。必须认真实施它们。
首先,天真的就地合并,如所述 这里不是正确的解决方案。它将性能降低至 O(N2)。
这个想法是对数组的一部分进行排序,同时使用其余部分作为合并的工作区域。
例如下面的合并函数。
它采用数组
xs
,两个排序的子数组分别表示为范围[i, m)
和[j, n)
。工作区域从w
开始。与大多数教科书中给出的标准合并算法相比,该算法在已排序的子数组和工作区之间交换内容。结果,前一个工作区包含合并后的排序元素,同时工作区中存储的前一个元素被移动到两个子数组中。但是,必须满足两个约束:
定义了这个合并算法后,很容易想象一个解决方案,它可以对数组的一半进行排序;接下来的问题是,如何处理工作区中剩余的未排序部分,如下所示:
一个直观的想法是对工作区的另一半进行递归排序,这样就只有 1/4 的元素没有排序然而。
这个阶段的关键点是我们必须合并已排序的1/4元素B
迟早会与已排序的 1/2 元素 A 一起使用。
工作区域是否剩余,仅容纳 1/4 元素,足够大以进行合并
A和B?不幸的是,事实并非如此。
然而,上面提到的第二个约束给了我们一个提示,如果我们能够确保合并顺序不会覆盖未合并的元素,我们可以通过将工作区域安排为与任一子数组重叠来利用它。
实际上,我们可以不对工作区域的后半部分进行排序,而是对前半部分进行排序,并将工作区域放在两个已排序的数组之间,如下所示:
这种设置有效地安排了工作区域与子数组 A 的重叠。
[Jyrki Katajainen、Tomi Pasanen、Jukka Teuhola] 中提出。 “实用的就地合并排序”。北欧计算杂志,1996]。
所以剩下的就是重复上面的步骤,将工作区域从1/2、1/4、1/8……缩小,当工作区域变得足够小(比如只剩下两个元素)时,我们可以切换到简单的插入排序来结束这个算法。
下面是基于本文的 ANSI C 实现。
其中 wmerge 是先前定义的。
完整的源代码可以在此处 详细说明可以在此处
找到这个版本不是最快的合并排序,因为它需要更多的交换操作。根据我的测试,它比标准版本更快,标准版本在每次递归中分配额外的空间。但它比优化版本慢,优化版本提前将原始数组加倍并使用它进行进一步合并。
Knuth left this as an exercise (Vol 3, 5.2.5). There do exist in-place merge sorts. They must be implemented carefully.
First, naive in-place merge such as described here isn't the right solution. It downgrades the performance to O(N2).
The idea is to sort part of the array while using the rest as working area for merging.
For example like the following merge function.
It takes the array
xs
, the two sorted sub-arrays are represented as ranges[i, m)
and[j, n)
respectively. The working area starts fromw
. Compare with the standard merge algorithm given in most textbooks, this one exchanges the contents between the sorted sub-array and the working area. As the result, the previous working area contains the merged sorted elements, while the previous elements stored in the working area are moved to the two sub-arrays.However, there are two constraints that must be satisfied:
With this merging algorithm defined, it's easy to imagine a solution, which can sort half of the array; The next question is, how to deal with the rest of the unsorted part stored in work area as shown below:
One intuitive idea is to recursive sort another half of the working area, thus there are only 1/4 elements haven't been sorted yet.
The key point at this stage is that we must merge the sorted 1/4 elements B
with the sorted 1/2 elements A sooner or later.
Is the working area left, which only holds 1/4 elements, big enough to merge
A and B? Unfortunately, it isn't.
However, the second constraint mentioned above gives us a hint, that we can exploit it by arranging the working area to overlap with either sub-array if we can ensure the merging sequence that the unmerged elements won't be overwritten.
Actually, instead of sorting the second half of the working area, we can sort the first half, and put the working area between the two sorted arrays like this:
This setup effectively arranges the work area overlap with the sub-array A. This idea
is proposed in [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. ``Practical in-place mergesort''. Nordic Journal of Computing, 1996].
So the only thing left is to repeat the above step, which reduces the working area from 1/2, 1/4, 1/8, … When the working area becomes small enough (for example, only two elements left), we can switch to a trivial insertion sort to end this algorithm.
Here is the implementation in ANSI C based on this paper.
Where wmerge is defined previously.
The full source code can be found here and the detailed explanation can be found here
By the way, this version isn't the fastest merge sort because it needs more swap operations. According to my test, it's faster than the standard version, which allocates extra spaces in every recursion. But it's slower than the optimized version, which doubles the original array in advance and uses it for further merging.
包括其“大结果”,本文描述了就地合并排序的几个变体 (PDF):
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf
移动次数较少的就地排序
Jyrki Katajainen、Tomi A. Pasanen
我认为这也是相关的。我手边有一份它的打印件,是一位同事传给我的,但我还没有读过。它似乎涵盖了基本理论,但我对该主题不够熟悉,无法判断其全面性:
http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681
最佳稳定合并
Antonios Symvonis
Including its "big result", this paper describes a couple of variants of in-place merge sort (PDF):
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf
In-place sorting with fewer moves
Jyrki Katajainen, Tomi A. Pasanen
I think this is relevant too. I have a printout of it lying around, passed on to me by a colleague, but I haven't read it. It seems to cover basic theory, but I'm not familiar enough with the topic to judge how comprehensively:
http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681
Optimal Stable Merging
Antonios Symvonis
这确实不容易或高效,我建议你不要这样做,除非你真的必须这样做(而且你可能不需要这样做,除非这是家庭作业,因为就地合并的应用大多是理论上的)。不能用快速排序来代替吗?无论如何,通过一些更简单的优化,快速排序会更快,并且它的额外内存是O(log N)。
无论如何,如果你必须这样做,那么你就必须这样做。这是我发现的: one 和 两个。我不熟悉就地合并排序,但似乎基本思想是使用旋转来促进合并两个数组而不使用额外的内存。
请注意,这甚至比非就地的经典合并排序还要慢。
It really isn't easy or efficient, and I suggest you don't do it unless you really have to (and you probably don't have to unless this is homework since the applications of inplace merging are mostly theoretical). Can't you use quicksort instead? Quicksort will be faster anyway with a few simpler optimizations and its extra memory is O(log N).
Anyway, if you must do it then you must. Here's what I found: one and two. I'm not familiar with the inplace merge sort, but it seems like the basic idea is to use rotations to facilitate merging two arrays without using extra memory.
Note that this is slower even than the classic merge sort that's not inplace.
关键的一步是让合并本身就位。这并不像那些消息来源所说的那么困难,但是当你尝试时你会失去一些东西。
查看合并的一个步骤:
我们知道排序序列小于其他所有内容,x小于所有内容A 中的 else ,并且 y 小于 B 中的其他所有内容。如果x小于或等于y,您只需将指针移动到A的开头。如果 y 小于 x,则必须将 y 洗牌到整个 A 排序。最后一步是导致成本高昂的原因(除非是退化的情况)。
通常,用一些空间换取时间并拥有一个可以在其之间来回排序的单独临时数组会更便宜(特别是当数组实际上每个元素仅包含单个单词时,例如,指向字符串或结构的指针)。
The critical step is getting the merge itself to be in-place. It's not as difficult as those sources make out, but you lose something when you try.
Looking at one step of the merge:
We know that the sorted sequence is less than everything else, that x is less than everything else in A, and that y is less than everything else in B. In the case where x is less than or equal to y, you just move your pointer to the start of A on one. In the case where y is less than x, you've got to shuffle y past the whole of A to sorted. That last step is what makes this expensive (except in degenerate cases).
It's generally cheaper (especially when the arrays only actually contain single words per element, e.g., a pointer to a string or structure) to trade off some space for time and have a separate temporary array that you sort back and forth between.
C 语言中无缓冲归并排序的示例。
自适应归并排序(优化)的示例。
添加支持代码和修改,以在任何大小的辅助缓冲区可用时加速合并(无需额外内存仍然可以工作)。使用前向和后向合并、环旋转、小序列合并和排序以及迭代合并排序。
An example of bufferless mergesort in C.
An example of adaptive mergesort (optimized).
Adds support code and modifications to accelerate the merge when an auxiliary buffer of any size is available (still works without additional memory). Uses forward and backward merging, ring rotation, small sequence merging and sorting, and iterative mergesort.
这个答案有一个代码示例,它实现了论文中描述的算法实用就地合并,作者:Bing-Chao Huang 和 Michael A. Langston。我不得不承认我不了解细节,但是给定的合并步骤的复杂度是 O(n)。
从实践的角度来看,有证据表明纯粹的就地实现在现实场景中并没有表现得更好。例如,C++ 标准定义了 std::inplace_merge,它是名称意味着就地合并操作。
假设 C++ 库通常经过很好的优化,看看它是如何实现的很有趣:
1) libstdc++(GCC 代码库的一部分):std::inplace_merge
实现委托给 __inplace_merge,这就避免了这个问题尝试分配临时缓冲区:
否则,它将回退到实现(__merge_without_buffer),不需要额外的内存,但不再以 O(n) 时间运行。
2) libc++ (Clang 代码库的一部分): std: :inplace_merge
看起来很相似。它委托给一个 函数,该函数还尝试分配缓冲区。根据是否获得足够的元素,它将选择实现。常量内存后备函数称为 __buffered_inplace_merge。
也许即使回退仍然是 O(n) 时间,但重点是,如果临时内存可用,他们不会使用该实现。
请注意,C++ 标准通过将所需的复杂性从 O(n) 降低到 O(N log N),明确给予实现选择此方法的自由:
当然,这不能证明永远不应该使用 O(n) 时间内的常量空间就地合并。另一方面,如果速度更快,优化的 C++ 库可能会切换到这种类型的实现。
This answer has a code example, which implements the algorithm described in the paper Practical In-Place Merging by Bing-Chao Huang and Michael A. Langston. I have to admit that I do not understand the details, but the given complexity of the merge step is O(n).
From a practical perspective, there is evidence that pure in-place implementations are not performing better in real world scenarios. For example, the C++ standard defines std::inplace_merge, which is as the name implies an in-place merge operation.
Assuming that C++ libraries are typically very well optimized, it is interesting to see how it is implemented:
1) libstdc++ (part of the GCC code base): std::inplace_merge
The implementation delegates to __inplace_merge, which dodges the problem by trying to allocate a temporary buffer:
Otherwise, it falls back to an implementation (__merge_without_buffer), which requires no extra memory, but no longer runs in O(n) time.
2) libc++ (part of the Clang code base): std::inplace_merge
Looks similar. It delegates to a function, which also tries to allocate a buffer. Depending on whether it got enough elements, it will choose the implementation. The constant-memory fallback function is called __buffered_inplace_merge.
Maybe even the fallback is still O(n) time, but the point is that they do not use the implementation if temporary memory is available.
Note that the C++ standard explicitly gives implementations the freedom to choose this approach by lowering the required complexity from O(n) to O(N log N):
Of course, this cannot be taken as a proof that constant space in-place merges in O(n) time should never be used. On the other hand, if it would be faster, the optimized C++ libraries would probably switch to that type of implementation.
这是我的C版本:
This is my C version:
我知道我迟到了,但这是我昨天写的解决方案。我也在其他地方发布了这个,但这似乎是 SO 上最流行的就地合并线程。我也没有在其他地方看到过这个算法,所以希望这对一些人有帮助。
该算法采用最简单的形式,以便于理解。可以对其进行显着调整以获得额外的速度。平均时间复杂度为:稳定就地数组合并为 O(n.log2n),整体排序为 O(n.(log2n)2)。
I know I'm late to the game, but here's a solution I wrote yesterday. I also posted this elsewhere, but this appears to be the most popular merge-in-place thread on SO. I've also not seen this algorithm posted anywhere else, so hopefully this helps some people.
This algorithm is in its most simple form so that it can be understood. It can be significantly tweaked for extra speed. Average time complexity is: O(n.log₂n) for the stable in-place array merge, and O(n.(log₂n)²) for the overall sort.
利用 C++ std::inplace_merge,就地合并排序可以实现如下:
https://github.com/DragonSpit/ParallelAlgorithms 存储库,它是开源且免费的。
Leveraging C++ std::inplace_merge, in-place merge sort can be implemented as follows:
More sorting algorithms, including parallel implementations, are available in https://github.com/DragonSpit/ParallelAlgorithms repo, which is open source and free.
我刚刚尝试使用插入排序算法在 JAVA 中进行合并排序,具体步骤如下。
1) 有两个排序数组可用。
2)比较每个数组的第一个值;并将最小值放入第一个数组中。
3)使用插入排序(从左到右遍历)将较大的值放入第二个数组中。
4)然后再次比较第一个数组的第二个值和第二个数组的第一个值,并执行相同的操作。但是,当交换发生时,有一些线索可以跳过比较其他项目,但只需交换即可。
我这里做了一些优化;在插入排序中保留较少的比较。
我发现此解决方案的唯一缺点是它需要在第二个数组中进行更大的数组元素交换。
例如)
第一个数组:3,7,8,9
第二个数组:1,2,4,5
然后 7,8,9 使第二个数组每次将其所有元素交换(左移一位)以将自己放入最后一个。
因此,与比较两个项目相比,这里交换项目的假设可以忽略不计。
https://github.com/skanagavelu/algorithams/blob/ master/src/sorting/MergeSort.java
I just tried in place merge algorithm for merge sort in JAVA by using the insertion sort algorithm, using following steps.
1) Two sorted arrays are available.
2) Compare the first values of each array; and place the smallest value into the first array.
3) Place the larger value into the second array by using insertion sort (traverse from left to right).
4) Then again compare the second value of first array and first value of second array, and do the same. But when swapping happens there is some clue on skip comparing the further items, but just swapping required.
I have made some optimization here; to keep lesser comparisons in insertion sort.
The only drawback i found with this solutions is it needs larger swapping of array elements in the second array.
e.g)
First___Array : 3, 7, 8, 9
Second Array : 1, 2, 4, 5
Then 7, 8, 9 makes the second array to swap(move left by one) all its elements by one each time to place himself in the last.
So the assumption here is swapping items is negligible compare to comparing of two items.
https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java