合并排序链接列表
我最近复习了一些基础知识,发现对链表进行合并排序是一个很好的挑战。 如果您有一个好的实现,请在这里展示。
I was recently brushing up on some fundamentals and found merge sorting a linked list to be a pretty good challenge. If you have a good implementation then show it off here.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(20)
很大程度上基于以下优秀代码:http://www.chiark。 greenend.org.uk/~sgtatham/algorithms/listsort.html
稍微修剪并整理:
注意:这是 O(NLog(N)) 保证,并且使用 O(1) 资源(无递归,无堆栈) , 没有什么)。
Heavily based on the EXCELLENT code from: http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
Trimmed slightly, and tidied:
NB: This is O(NLog(N)) guaranteed, and uses O(1) resources (no recursion, no stack, nothing).
这是使用Swift 编程语言的解决方案。
这里是节点类 & getMiddle方法
Here is the solution using Swift Programming Language.
And here are the Node Class & getMiddle Method
嘿,我知道这个答案有点晚了,但得到了一个快速简单的答案。
代码采用 F# 语言,但适用于任何语言。 由于这是 ML 家族中不常见的语言,因此我将给出一些要点来增强可读性。
F# 是通过制表完成嵌套的。 函数中的最后一行代码(嵌套部分)是返回值。 (x,y)是一个元组,x::xs是头x和尾xs的列表(其中xs可以为空),|> 将最后一行的结果通过管道作为其右侧表达式的参数(增强可读性),最后一个 (fun args -> some expression) 是一个 lambda 函数。
重要的是要注意,这是完全尾递归的,因此不存在堆栈溢出的可能性,并且通过首先将列表一次性扩展为单例列表,我们降低了最差成本的常数因子。 由于合并是在列表的列表上进行的,因此我们可以递归地合并和排序内部列表,直到到达所有内部列表都被排序到一个列表的固定点,然后我们返回该列表,从而从列表列表折叠为列表再次。
Hey I know that this is a bit late an answer but got a fast simple one.
The code is in F# but will goes in any language. Since this is an uncommen language of the ML family, I'll give some point to enhance the readability.
F# are nesting done by tabulating. the last line of code in a function (nested part) are the return value. (x, y) is a tuple, x::xs is a list of the head x and tail xs (where xs can be empty), |> take the result of last line an pipe it as argument to the expression right of it (readability enhancing) and last (fun args -> some expression) are a lambda function.
It is important to notice that this is fully tail recursive so no possibility of stack overflow, and by first expanding the list to a singleton list list in one go we, lower the constant factor on the worst cost. Since merge are working on list of list, we can recursively merge and sort the inner list until we get to the fix point where all inner list are sorted into one list and then we return that list, hence collapsing from a list list to a list again.
下面是链表归并排序的 Java 实现:
Here is the Java Implementation of Merge Sort on Linked List:
我没有看到这里发布任何 C++ 解决方案。 那么,就这样吧。 希望它能帮助某人。
I don't see any C++ solutions posted here. So, here it goes. Hope it helps someone.
这是完整的代码,它展示了如何在 java 中创建链接列表并使用合并排序对其进行排序。 我正在 MergeNode 类中创建节点,还有另一个类 MergesortLinklist,其中存在划分和合并逻辑。
This is the entire Piece of code which shows how we can create linklist in java and sort it using Merge sort. I am creating node in MergeNode class and there is another class MergesortLinklist where there is divide and merge logic.
最简单的 Java 实现:
Simplest Java Implementation:
链表非递归合并排序的另一个示例,其中函数不是类的一部分。 此示例代码和 HP / Microsoft
std::list::sort
都使用相同的基本算法。 自下而上的非递归归并排序,使用指向列表第一个节点的小型(26 到 32)指针数组,其中 array[i] 为 0 或指向列表大小为 2 的 i 次方。 在我的系统 Intel 2600K 3.4ghz 上,它可以在大约 1 秒内对 400 万个节点的 32 位无符号整数作为数据进行排序。Visual Studio 2015 将
std::list::sort
更改为基于迭代器而不是列表,并且还更改为自上而下的合并排序,这需要扫描的开销。 我最初认为需要切换到自上而下才能与迭代器一起使用,但是当再次询问时,我对此进行了调查并确定不需要切换到较慢的自上而下方法,并且可以使用自下而上来实现相同的基于迭代器的逻辑。 此链接中的答案解释了这一点,并提供了一个独立的示例以及包含文件“list”中 VS2019 的std::list::sort()
的替代品。`std::list<>:: sort()` - 为什么突然切换到自上而下的策略?
Another example of a non-recursive merge sort for linked lists, where the functions are not part of a class. This example code and HP / Microsoft
std::list::sort
both use the same basic algorithm. A bottom up, non-recursive, merge sort that uses a small (26 to 32) array of pointers to the first nodes of a list, wherearray[i]
is either 0 or points to a list of size 2 to the power i. On my system, Intel 2600K 3.4ghz, it can sort 4 million nodes with 32 bit unsigned integers as data in about 1 second.Visual Studio 2015 changed
std::list::sort
to be based on iterators instead of lists, and also changed to a top down merge sort, which requires the overhead of scanning. I initially assumed that the switch to top down was needed to work with the iterators, but when it was asked about again, I investigated this and determined that the switch to the slower top down method was not needed, and bottom up could be implemented using the same iterator based logic. The answer in this link explains this and provide a stand-alone example as well as a replacement for VS2019'sstd::list::sort()
in the include file "list".`std::list<>::sort()` - why the sudden switch to top-down strategy?
一个经过测试、有效的
C++
版本的单链表,基于得票最高的答案。singlelinkedlist.h:
main.cpp:
A tested, working
C++
version of single linked list, based on the highest voted answer.singlelinkedlist.h:
main.cpp:
mono eglib< 中有一个非递归链表合并排序/a>.
基本思想是各种合并的控制循环与二进制整数的按位增量并行。 有 O(n) 次合并将 n 个节点“插入”到合并树中,这些合并的排名对应于递增的二进制数字。 使用此类比,只需将合并树的 O(log n) 个节点具体化为临时保存数组。
There's a non-recursive linked-list mergesort in mono eglib.
The basic idea is that the control-loop for the various merges parallels the bitwise-increment of a binary integer. There are O(n) merges to "insert" n nodes into the merge tree, and the rank of those merges corresponds to the binary digit that gets incremented. Using this analogy, only O(log n) nodes of the merge-tree need to be materialized into a temporary holding array.
这是我对 Knuth 的“列表合并排序”的实现(来自 TAOCP 第 3 卷第 2 版的算法 5.2.4L)。 我将在最后添加一些注释,但这里有一个摘要:
在随机输入上,它的运行速度比 Simon Tatham 的代码快一点(请参阅 Dave Gamble 的非递归答案,带有链接),但比 Dave Gamble 的递归代码慢一点。 它比任何一个都更难理解。 至少在我的实现中,它要求每个元素有两个指向元素的指针。 (另一种选择是一个指针和一个布尔标志。)因此,这可能不是一种有用的方法。 然而,一个显着的一点是,如果输入具有已经排序的长段,则它运行得非常快。
Here is my implementation of Knuth's "List merge sort" (Algorithm 5.2.4L from Vol.3 of TAOCP, 2nd ed.). I'll add some comments at the end, but here's a summary:
On random input, it runs a bit faster than Simon Tatham's code (see Dave Gamble's non-recursive answer, with a link) but a bit slower than Dave Gamble's recursive code. It's harder to understand than either. At least in my implementation, it requires each element to have TWO pointers to elements. (An alternative would be one pointer and a boolean flag.) So, it's probably not a useful approach. However, one distinctive point is that it runs very fast if the input has long stretches that are already sorted.
我决定测试这里的示例,以及另一种方法,最初由 Jonathan Cunningham 在 Pop-11 中编写。 我用 C# 编写了所有方法,并与一系列不同的列表大小进行了比较。 我比较了 Raja R Harinath 的 Mono eglib 方法、Shital Shah 的 C# 代码、Jayadev 的 Java 方法、David Gamble 的递归和非递归版本、Ed Wynn 的第一个 C 代码(这与我的示例数据集崩溃了,我没有调试)和坎宁安的版本。 完整代码位于:https://gist.github.com/314e572808f29adb0e41.git。
Mono eglib 基于与 Cunningham 类似的想法,并且速度相当,除非列表恰好已经排序,在这种情况下 Cunningham 的方法要快得多(如果部分排序,eglib 会稍微快一些)。 eglib 代码使用固定表来保存合并排序递归,而 Cunningham 的方法通过使用递增级别的递归来工作 - 因此它开始使用无递归,然后是 1 深度递归,然后是 2 深度递归,依此类推,根据需要多少步来进行排序。 我发现坎宁安代码更容易理解,并且无需猜测递归表的大小,因此它得到了我的投票。 我在此页面尝试的其他方法速度慢两倍或更多倍。
下面是 Pop-11 排序的 C# 端口:
I decided to test the examples here, and also one more approach, originally written by Jonathan Cunningham in Pop-11. I coded all the approaches in C# and did a comparison with a range of different list sizes. I compared the Mono eglib approach by Raja R Harinath, the C# code by Shital Shah, the Java approach by Jayadev, the recursive and non-recursive versions by David Gamble, the first C code by Ed Wynn (this crashed with my sample dataset, I didn't debug), and Cunningham's version. Full code here: https://gist.github.com/314e572808f29adb0e41.git.
Mono eglib is based on a similar idea to Cunningham's and is of comparable speed, unless the list happens to be sorted already, in which case Cunningham's approach is much much faster (if its partially sorted, the eglib is slightly faster). The eglib code uses a fixed table to hold the merge sort recursion, whereas Cunningham's approach works by using increasing levels of recursion - so it starts out using no recursion, then 1-deep recursion, then 2-deep recursion and so on, according to how many steps are needed to do the sort. I find the Cunningham code a little easier to follow and there is no guessing involved in how big to make the recursion table, so it gets my vote. The other approaches I tried from this page were two or more times slower.
Here is the C# port of the Pop-11 sort:
我一直痴迷于优化该算法的混乱,下面是我最终得到的结果。 互联网和 StackOverflow 上的许多其他代码都非常糟糕。 有人试图获得列表的中间点,进行递归,对剩余节点进行多个循环,维护大量事物的计数 - 所有这些都是不必要的。 归并排序自然适合链表,算法可以很漂亮且紧凑,但要达到这种状态并不容易。
据我所知,下面的代码维护了最少数量的变量,并且具有算法所需的最少逻辑步骤(即不会使代码不可维护/不可读)。 然而,我并没有尝试最小化 LOC 并保留尽可能多的空白以保持可读性。 我已经通过相当严格的单元测试测试了这段代码。
请注意,此答案结合了其他答案https://stackoverflow.com/a/3032462/207661中的一些技术。 虽然代码是用 C# 编写的,但转换为 C++、Java 等应该很简单。
兴趣点
更新:@ideasman42 将上述代码翻译为 C/C++ 以及修复注释和位的建议更多改进。 上面的代码现在已与这些代码保持同步。
I'd been obsessing over optimizing clutter for this algorithm and below is what I've finally arrived at. Lot of other code on Internet and StackOverflow is horribly bad. There are people trying to get middle point of the list, doing recursion, having multiple loops for left over nodes, maintaining counts of ton of things - ALL of which is unnecessary. MergeSort naturally fits to linked list and algorithm can be beautiful and compact but it's not trivial to get to that state.
Below code maintains minimum number of variables and has minimum number of logical steps needed for the algorithm (i.e. without making code unmaintainable/unreadable) as far as I know. However I haven't tried to minimize LOC and kept as much white space as necessary to keep things readable. I've tested this code through fairly rigorous unit tests.
Note that this answer combines few techniques from other answer https://stackoverflow.com/a/3032462/207661. While the code is in C#, it should be trivial to convert in to C++, Java, etc.
Points of interest
Update: @ideasman42 has translated above code to C/C++ along with suggestions for fixing comments and bit more improvement. Above code is now up to date with these.
这是一个替代的递归版本。 这不需要沿着列表进行分割:我们提供一个指向头元素(不是排序的一部分)的指针和一个长度,并且递归函数返回一个指向已排序列表末尾的指针。
Here's an alternative recursive version. This does not need to step along the list to split it: we supply a pointer to a head element (which is not part of the sort) and a length, and the recursive function returns a pointer to the end of the sorted list.
一种有趣的方法是维护一个堆栈,只有当堆栈上的列表具有相同数量的元素时才合并,否则推送列表,直到用完传入列表中的元素,然后向上合并堆栈。
One interesting way is to maintain a stack, and only merge if the list on the stack has the same number of elements, and otherwise push the list, until you run out of elements in the incoming list, and then merge up the stack.
最简单的是来自
Gonnet + Baeza Yates 算法手册。 您可以使用所需的排序元素数量来调用它,该元素会递归地一分为二,直到达到对大小为一的列表的请求,然后您只需将原始列表的前面剥离即可。 这些都被合并到一个完整大小的排序列表中。
[请注意,第一篇文章中基于堆栈的很酷的排序称为在线合并排序,它在 Knuth 第 3 卷的练习中被提及得最少]
The simplest is from
Gonnet + Baeza Yates Handbook of Algorithms. You call it with the number of sorted elements you want, which recursively gets bisected until it reaches a request for a size one list which you then just peel off the front of the original list. These all get merged up into a full sized sorted list.
[Note that the cool stack-based one in the first post is called the Online Mergesort and it gets the tiniest mention in an exercise in Knuth Vol 3]
想知道为什么它应该是一个巨大的挑战,正如这里所述,这里是一个简单的 Java 实现,没有任何“聪明的技巧”。
Wonder why it should be big challenge as it is stated here, here is a straightforward implementation in Java with out any "clever tricks".
更简单/更清晰的实现可能是递归实现,从中 NLog(N) 执行时间更清晰。
注意:这对递归有 Log(N) 存储要求。 性能应该与我发布的其他策略大致相当。 这里有一个潜在的优化,通过运行合并循环 while (list && right),并简单地附加剩余的列表(因为我们并不真正关心列表的末尾;知道它们被合并就足够了) 。
A simpler/clearer implementation might be the recursive implementation, from which the NLog(N) execution time is more clear.
NB: This has a Log(N) storage requirement for the recursion. Performance should be roughly comparable with the other strategy I posted. There is a potential optimisation here by running the merge loop while (list && right), and simple appending the remaining list (since we don't really care about the end of the lists; knowing that they're merged suffices).