简体   繁体   English

迭代,就地归并排序

[英]iterative, In-place MergeSort

For my algorithms class, we were tasked with writing a implementation of merge sort that is iterative instead of recursive, and in-place instead of requiring another array.对于我的算法课,我们的任务是编写一个迭代而不是递归的合并排序实现,并且就地而不是需要另一个数组。 Since this for a class, I don't want any code given to me, but I cannot figure out the basic algorithm of what to do.由于这是一个班级,我不想给我任何代码,但我无法弄清楚要做什么的基本算法。 A google search doesn't give anything besides code, or explanations that I don't understand, so that isn't a help.除了代码或我不明白的解释之外,谷歌搜索不提供任何内容,因此这没有帮助。

What I currently understand to do is sort all the sub-arrays of size 1 (which is of course trivial), then merge those of size 2, size 4, and so on, but this seems much closer to insertion sort, and then there is the issue of how to use a constant amount of extra space.我目前理解要做的是对大小为 1 的所有子数组进行排序(这当然是微不足道的),然后合并大小为 2、大小为 4 等的子数组,但这似乎更接近插入排序,然后有是如何使用恒定量的额外空间的问题。

As a final note, I am not allowed to use any C++ standard library functions or classes, such as vectors, stacks, any of the sorts, etc.最后一点,我不允许使用任何 C++ 标准库函数或类,例如向量、堆栈、任何种类等。

An iterative algorithm that uses a "kind of" merge sort to sort an array in-place could look like below.使用“某种”合并排序对数组进行就地排序的迭代算法可能如下所示。

Let's take this unsorted array as example:我们以这个未排序的数组为例:

4, 3, 8, 5, 9, 2, 5, 1, 7, 10, 8, 0, 3

The algorithm will have an outer loop on the size of the merged arrays.该算法将对合并数组的大小进行外循环。 So at the start this size is 1 (nothing has been merged yet).所以一开始这个大小是1(还没有合并)。 Then in each iteration this size is doubled, which effectively takes two already sorted, consecutive segments together in a merge.然后在每次迭代中,这个大小加倍,这实际上将两个已经排序的连续段合并在一起。

The actual merge will use two indexes in the to-be-merged segment: one at the start of each of the two segments that are to be merged.实际的合并将在要合并的段中使用两个索引:一个位于要合并的两个段中的每一个的开头。

As long as the value at the left index is less or equal to the other value, the left index is incremented (moves to the right).只要左侧索引处的值小于或等于另一个值,左侧索引就会递增(向右移动)。 In the other case the most complex operation in this algorithm is executed:在另一种情况下,执行此算法中最复杂的操作:

The values between the two indexes are shifted to the right and the rightmost of those (the value at the second index) is moved to the first index.两个索引之间的值向右移动,最右边的值(第二个索引处的值)移动到第一个索引。 So the values cycle around one position.因此,值围绕一个位置循环。 After this cycle, both indexes are incremented.在这个循环之后,两个索引都会增加。

This process is repeated until either the left index reaches the right index, or the right index reaches the end of the second segment (which can be the end of the array).重复此过程,直到左索引到达右索引,或者右索引到达第二段的末尾(可以是数组的末尾)。 When this happens the merge is complete, and the two segments will be considered as one in the next iteration of the outer loop.当这种情况发生时,合并就完成了,在外循环的下一次迭代中,这两个段将被视为一个段。

Here are some images illustrating these steps as they would be performed on the example data:以下是一些图像,说明了将在示例数据上执行的这些步骤:

在此处输入图片说明

Here the merge is performed to make segments of size two.此处执行合并以生成大小为 2 的段。 Sometimes the last segment will not have a full size, but that is not a problem.有时最后一个段不会有完整的大小,但这不是问题。 For each of the colored segments, the two indexes are placed, one pointing at the first of the two values, and the other to the second value.对于每个彩色段,放置了两个索引,一个指向两个值中的第一个,另一个指向第二个值。 Where the first value is greater than the second, they are swapped.如果第一个值大于第二个值,则交换它们。 In case of a swap both indexes are incremented and the second index reaches the end of the second segment (which was only 1 value wide), and so the merge ends.在交换的情况下,两个索引都会增加,第二个索引到达第二个段的末尾(只有 1 个值宽),因此合并结束。 In case no swap happens, only the first index increments, but reaches the second one, so then also the merge ends (without having made changes).如果没有交换发生,只有第一个索引增加,但到达第二个索引,因此合并也结束(没有进行更改)。

It becomes more interesting in the second iteration of the outer loop:在外循环的第二次迭代中变得更有趣:

在此处输入图片说明

The first segments to be joined are [3 4] and [5 8].要加入的第一段是 [3 4] 和 [5 8]。 The two indexes point to 3 and 5 respectively (underlined in blue).两个索引分别指向 3 和 5(蓝色下划线)。 The left index is incremented as long as the corresponding value is not greater than the value at the second index.只要对应的值不大于第二个索引处的值,左索引就会递增。 In this case that means the first index reaches the second without any changes.在这种情况下,这意味着第一个索引到达第二个索引而没有任何更改。 For the second pair of segments, there is more work to do:对于第二对段,还有更多工作要做:

在此处输入图片说明

Now [2 9] and [1 5] need to be merged.现在需要合并 [2 9] 和 [1 5]。 As 2 is greater than 1 the cycling operation kicks in: 1 has to be shifted in, pushing the 2 and 9 one position to the right.由于 2 大于 1,循环操作开始:1 必须移入,将 2 和 9 向右推一个位置。 Both indexes are incremented.两个索引都递增。 Now 2 is not greater than the other value (5), so only the first index is incremented.现在 2 不大于另一个值 (5),因此只增加第一个索引。 Finally, 9 is greater than 5, so they need to be swapped, and then the merge is complete for these segments.最后,9大于5,所以需要对它们进行交换,然后对这些段完成合并。

A similar sequence of operations is executed for the next pair of segments:对下一对段执行类似的操作序列:

在此处输入图片说明

The last "pair" of segments, really does not have a second segment: the second index points beyond the array end, so there the merge stops immediately.最后的“对”段实际上没有第二个段:第二个索引指向数组末尾之外,因此合并立即停止。

Again the outer loop iterates, doubling the segment size.外循环再次迭代,将段大小加倍。 Now the following has to be merged:现在必须合并以下内容:

在此处输入图片说明

Note how the 1 (at the second index) is shifted in before [3 4 5 8], which all move one position to the right to make room for it.请注意 1(在第二个索引处)是如何在 [3 4 5 8] 之前移入的,这些都将向右移动一个位置以为其腾出空间。 The same happens with the 2: it is shifted in before the same 4 values again. 2 也会发生同样的情况:它再次移入相同的 4 个值之前。 But then we find that 3 is not greater than five, and the first index increments until it points to the 8. There the 5 is injected before the 8. Finally, 8 is not greater than 9 so the second index reaches the end point.但是后来我们发现3不大于5,第一个索引递增,直到它指向8。5在8之前被注入。最后,8不大于9,所以第二个索引到达终点。

I will not present the same for the other segment, and the final iteration of the outer loop, which will do one more merge.我不会为其他段和外循环的最终迭代提供相同的内容,这将再进行一次合并。

As you requested, I provide no code ;-)正如你所要求的,我没有提供代码;-)

Some considerations一些注意事项

Although this could be named a merge sort, the cycling of values really defeats the efficiency of the original algorithm in the worst case scenarios.尽管这可以称为归并排序,但在最坏的情况下,值的循环确实会降低原始算法的效率。 True, the number of comparisons is still the same, but the number of moves can be more.确实,比较的次数仍然相同,但移动的次数可以更多。 Take for instance the values [3 4 5 8] which are moved twice to make room for a moving 1 and moving 2. This already totals to 10 moves (and the merge in that step is not yet complete), while the original merge sort will always make the same number of moves as there are values in the segments that are being merged.以值 [3 4 5 8] 为例,它们移动了两次,为移动 1 和移动 2 腾出空间。这已经总计 10 次移动(并且该步骤中的合并尚未完成),而原始合并排序将始终进行与正在合并的段中的值相同数量的移动。 In the better cases, this algorithm needs no movements at all, or fewer than the original algorithm.在更好的情况下,该算法根本不需要移动,或者比原始算法更少。

This method guarantees a stable sort.这种方法保证了稳定的排序。

You mention constant space and in place.你提到了恒定的空间和位置。

If the in place merge sort doesn't have to be stable, you can swap elements instead of moving them between arrays.如果就地归并排序不必稳定,您可以交换元素而不是在数组之间移动它们。 Do a merge sort on the left half of the array into the right half of the array, swapping the merged output with the right half of the array, so that the merged output ends up in the right half of the array, and what was in the right half of the array ends up swapped to the left half of the array, reordered (this is the unstable part) but unsorted.对数组的左半部分进行归并排序到数组的右半部分,将合并后的输出与数组的右半部分交换,这样合并后的输出最终会出现在数组的右半部分,以及里面的内容数组的右半部分最终交换到数组的左半部分,重新​​排序(这是不稳定的部分)但未排序。

Merge sort the 2nd quarter into the 1st quarter of the array, so that the 1st quarter ends up with sorted data, the 2nd quarter ends up with reordered but unsorted data.将第 2 季度合并排序到数组的第 1 季度,以便第 1 季度以排序数据结束,第 2 季度以重新排序但未排序的数据结束。 Then merge the 1st quarter with the 2nd half of the array into the array starting at the beginning of the 2nd quarter again swapping during the merge operation.然后将数组的第 1 季度和第 2 部分合并到从第 2 季度开始的数组中,并在合并操作期间再次交换。 The last 3/4s of the array ends up sorted, and the first 1/4 array is reordered and unsorted.数组的最后 3/4 以排序结束,第一个 1/4 数组重新排序且未排序。

Merge sort 2nd eighth into 1st eighth, then merge sort 1st eighth with last 3/4 of the array, ending up with last 7/8 sorted and first 1/8 reordered but unsorted.将第 2 个第 8 位合并到第 1 个第 8 位,然后将第 1 个第 8 位与数组的最后 3/4 合并排序,最后 7/8 位已排序,前 1/8 位已重新排序但未排序。

Continue this until there is only one or two unsorted elements on the left.继续这样做,直到左侧只有一两个未排序的元素。 These can be put in place doing a partial insertion sort for the one or two elements.这些可以放置到位,对一个或两个元素进行部分插入排序。


If using constant space and the merge sort needs to be stable, you can use bottom up merge sort, moving part of the array into the constant space, merge into the now freed space in the array, then repack the array to fill in the one or two gaps left behind by the merge process, and repeat until you reach the last remaining freed part of the array, at which point you can do a normal merge sort using the constant space and the freed part of the array.如果使用常量空间并且归并排序需要稳定,可以使用自底向上归并排序,将数组的一部分移入常量空间,合并到数组中现在释放的空间中,然后重新打包数组填充一个或合并过程留下的两个间隙,并重复直到到达数组的最后剩余的释放部分,此时您可以使用常量空间和数组的释放部分进行正常的合并排序。 This completes one pass of the merge sort.这样就完成了一次归并排序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM