我最近在复习一些基础知识,发现将链表进行归并排序是一个相当不错的挑战。如果您有良好的实现,请在这里展示出来。
我最近在复习一些基础知识,发现将链表进行归并排序是一个相当不错的挑战。如果您有良好的实现,请在这里展示出来。
不明白为什么这应该是一个巨大的挑战,这里有一个直接的Java实现,没有任何“聪明的技巧”。
//The main function
public static Node merge_sort(Node head)
{
if(head == null || head.next == null)
return head;
Node middle = getMiddle(head); //get the middle of the list
Node left_head = head;
Node right_head = middle.next;
middle.next = null; //split the list into two halfs
return merge(merge_sort(left_head), merge_sort(right_head)); //recurse on that
}
//Merge subroutine to merge two sorted lists
public static Node merge(Node a, Node b)
{
Node dummyHead = new Node();
for(Node current = dummyHead; a != null && b != null; current = current.next;)
{
if(a.data <= b.data)
{
current.next = a;
a = a.next;
}
else
{
current.next = b;
b = b.next;
}
}
dummyHead.next = (a == null) ? b : a;
return dummyHead.next;
}
//Finding the middle element of the list for splitting
public static Node getMiddle(Node head)
{
if(head == null)
return head;
Node slow = head, fast = head;
while(fast.next != null && fast.next.next != null)
{
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
一种更简单/更清晰的实现可能是递归实现,从中可以更清楚地看出NLog(N)的执行时间。
typedef struct _aList {
struct _aList* next;
struct _aList* prev; // Optional.
// some data
} aList;
aList* merge_sort_list_recursive(aList *list,int (*compare)(aList *one,aList *two))
{
// Trivial case.
if (!list || !list->next)
return list;
aList *right = list,
*temp = list,
*last = list,
*result = 0,
*next = 0,
*tail = 0;
// Find halfway through the list (by running two pointers, one at twice the speed of the other).
while (temp && temp->next)
{
last = right;
right = right->next;
temp = temp->next->next;
}
// Break the list in two. (prev pointers are broken here, but we fix later)
last->next = 0;
// Recurse on the two smaller lists:
list = merge_sort_list_recursive(list, compare);
right = merge_sort_list_recursive(right, compare);
// Merge:
while (list || right)
{
// Take from empty lists, or compare:
if (!right) {
next = list;
list = list->next;
} else if (!list) {
next = right;
right = right->next;
} else if (compare(list, right) < 0) {
next = list;
list = list->next;
} else {
next = right;
right = right->next;
}
if (!result) {
result=next;
} else {
tail->next=next;
}
next->prev = tail; // Optional.
tail = next;
}
return result;
}
注意:这个递归算法需要Log(N)的存储空间。性能方面应该与我之前发布的另一种策略大致相当。可以通过在(list && right)运行合并循环并简单地附加剩余列表(因为我们不关心列表的结尾;知道它们已经合并就足够了)来进行潜在的优化。
本文内容大部分基于这篇优秀代码:http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
稍作修改和整理:
typedef struct _aList {
struct _aList* next;
struct _aList* prev; // Optional.
// some data
} aList;
aList *merge_sort_list(aList *list,int (*compare)(aList *one,aList *two))
{
int listSize=1,numMerges,leftSize,rightSize;
aList *tail,*left,*right,*next;
if (!list || !list->next) return list; // Trivial case
do { // For each power of two<=list length
numMerges=0,left=list;tail=list=0; // Start at the start
while (left) { // Do this list_len/listSize times:
numMerges++,right=left,leftSize=0,rightSize=listSize;
// Cut list into two halves (but don't overrun)
while (right && leftSize<listSize) leftSize++,right=right->next;
// Run through the lists appending onto what we have so far.
while (leftSize>0 || (rightSize>0 && right)) {
// Left empty, take right OR Right empty, take left, OR compare.
if (!leftSize) {next=right;right=right->next;rightSize--;}
else if (!rightSize || !right) {next=left;left=left->next;leftSize--;}
else if (compare(left,right)<0) {next=left;left=left->next;leftSize--;}
else {next=right;right=right->next;rightSize--;}
// Update pointers to keep track of where we are:
if (tail) tail->next=next; else list=next;
// Sort prev pointer
next->prev=tail; // Optional.
tail=next;
}
// Right is now AFTER the list we just sorted, so start the next sort there.
left=right;
}
// Terminate the list, double the list-sort size.
tail->next=0,listSize<<=1;
} while (numMerges>1); // If we only did one merge, then we just sorted the whole list.
return list;
}
一种有趣的方法是维护一个栈,只有当栈顶上的列表有相同数量的元素时才合并,否则将列表压入栈中,直到传入的列表中没有元素为止,然后向上合并整个栈。
(请注意,第一个帖子中的基于堆栈的酷炫算法称为在线归并排序,它在Knuth第3卷的一个练习中只有极小的提及。)
SingleListNode<T> SortLinkedList<T>(SingleListNode<T> head) where T : IComparable<T>
{
int blockSize = 1, blockCount;
do
{
//Maintain two lists pointing to two blocks, left and right
SingleListNode<T> left = head, right = head, tail = null;
head = null; //Start a new list
blockCount = 0;
//Walk through entire list in blocks of size blockCount
while (left != null)
{
blockCount++;
//Advance right to start of next block, measure size of left list while doing so
int leftSize = 0, rightSize = blockSize;
for (;leftSize < blockSize && right != null; ++leftSize)
right = right.Next;
//Merge two list until their individual ends
bool leftEmpty = leftSize == 0, rightEmpty = rightSize == 0 || right == null;
while (!leftEmpty || !rightEmpty)
{
SingleListNode<T> smaller;
//Using <= instead of < gives us sort stability
if (rightEmpty || (!leftEmpty && left.Value.CompareTo(right.Value) <= 0))
{
smaller = left; left = left.Next; --leftSize;
leftEmpty = leftSize == 0;
}
else
{
smaller = right; right = right.Next; --rightSize;
rightEmpty = rightSize == 0 || right == null;
}
//Update new list
if (tail != null)
tail.Next = smaller;
else
head = smaller;
tail = smaller;
}
//right now points to next block for left
left = right;
}
//terminate new list, take care of case when input list is null
if (tail != null)
tail.Next = null;
//Lg n iterations
blockSize <<= 1;
} while (blockCount > 1);
return head;
}
注意事项
更新:@ideasman42已经将上述代码翻译成了C/C++,并提出了修复注释和更多改进的建议。上面的代码现在已经更新。
p q & k
,我认为应该分别是 left right & block_size
。 - ideasman42element* mergesort(element *head,long lengtho)
{
long count1=(lengtho/2), count2=(lengtho-count1);
element *next1,*next2,*tail1,*tail2,*tail;
if (lengtho<=1) return head->next; /* Trivial case. */
tail1 = mergesort(head,count1);
tail2 = mergesort(tail1,count2);
tail=head;
next1 = head->next;
next2 = tail1->next;
tail1->next = tail2->next; /* in case this ends up as the tail */
while (1) {
if(cmp(next1,next2)<=0) {
tail->next=next1; tail=next1;
if(--count1==0) { tail->next=next2; return tail2; }
next1=next1->next;
} else {
tail->next=next2; tail=next2;
if(--count2==0) { tail->next=next1; return tail1; }
next2=next2->next;
}
}
}
/// <summary>
/// Sort a linked list in place. Returns the sorted list.
/// Originally by Jonathan Cunningham in Pop-11, May 1981.
/// Ported to C# by Jon Meyer.
/// </summary>
public class ListSorter<T> where T : IComparable<T> {
SingleListNode<T> workNode = new SingleListNode<T>(default(T));
SingleListNode<T> list;
/// <summary>
/// Sorts a linked list. Returns the sorted list.
/// </summary>
public SingleListNode<T> Sort(SingleListNode<T> head) {
if (head == null) throw new NullReferenceException("head");
list = head;
var run = GetRun(); // get first run
// As we progress, we increase the recursion depth.
var n = 1;
while (list != null) {
var run2 = GetSequence(n);
run = Merge(run, run2);
n++;
}
return run;
}
// Get the longest run of ordered elements from list.
// The run is returned, and list is updated to point to the
// first out-of-order element.
SingleListNode<T> GetRun() {
var run = list; // the return result is the original list
var prevNode = list;
var prevItem = list.Value;
list = list.Next; // advance to the next item
while (list != null) {
var comp = prevItem.CompareTo(list.Value);
if (comp > 0) {
// reached end of sequence
prevNode.Next = null;
break;
}
prevItem = list.Value;
prevNode = list;
list = list.Next;
}
return run;
}
// Generates a sequence of Merge and GetRun() operations.
// If n is 1, returns GetRun()
// If n is 2, returns Merge(GetRun(), GetRun())
// If n is 3, returns Merge(Merge(GetRun(), GetRun()),
// Merge(GetRun(), GetRun()))
// and so on.
SingleListNode<T> GetSequence(int n) {
if (n < 2) {
return GetRun();
} else {
n--;
var run1 = GetSequence(n);
if (list == null) return run1;
var run2 = GetSequence(n);
return Merge(run1, run2);
}
}
// Given two ordered lists this returns a list that is the
// result of merging the two lists in-place (modifying the pairs
// in list1 and list2).
SingleListNode<T> Merge(SingleListNode<T> list1, SingleListNode<T> list2) {
// we reuse a single work node to hold the result.
// Simplifies the number of test cases in the code below.
var prevNode = workNode;
while (true) {
if (list1.Value.CompareTo(list2.Value) <= 0) {
// list1 goes first
prevNode.Next = list1;
prevNode = list1;
if ((list1 = list1.Next) == null) {
// reached end of list1 - join list2 to prevNode
prevNode.Next = list2;
break;
}
} else { // same but for list2
prevNode.Next = list2;
prevNode = list2;
if ((list2 = list2.Next) == null) {
prevNode.Next = list1;
break;
}
}
}
// the result is in the back of the workNode
return workNode.Next;
}
}
std::list::sort
使用相同的基本算法。这是一种自底向上、非递归的归并排序,使用一个小型(26到32个)指向列表第一个节点的指针数组,其中array[i]
要么为0,要么指向大小为2的i次方的列表。在我的系统上,Intel 2600K 3.4ghz,它可以在大约1秒钟内对具有32位无符号整数数据的400万个节点进行排序。typedef struct NODE_{
struct NODE_ * next;
uint32_t data;
}NODE;
NODE * MergeLists(NODE *, NODE *); /* prototype */
/* sort a list using array of pointers to list */
/* aList[i] == NULL or ptr to list with 2^i nodes */
#define NUMLISTS 32 /* number of lists */
NODE * SortList(NODE *pList)
{
NODE * aList[NUMLISTS]; /* array of lists */
NODE * pNode;
NODE * pNext;
int i;
if(pList == NULL) /* check for empty list */
return NULL;
for(i = 0; i < NUMLISTS; i++) /* init array */
aList[i] = NULL;
pNode = pList; /* merge nodes into array */
while(pNode != NULL){
pNext = pNode->next;
pNode->next = NULL;
for(i = 0; (i < NUMLISTS) && (aList[i] != NULL); i++){
pNode = MergeLists(aList[i], pNode);
aList[i] = NULL;
}
if(i == NUMLISTS) /* don't go beyond end of array */
i--;
aList[i] = pNode;
pNode = pNext;
}
pNode = NULL; /* merge array into one list */
for(i = 0; i < NUMLISTS; i++)
pNode = MergeLists(aList[i], pNode);
return pNode;
}
/* merge two already sorted lists */
/* compare uses pSrc2 < pSrc1 to follow the STL rule */
/* of only using < and not <= */
NODE * MergeLists(NODE *pSrc1, NODE *pSrc2)
{
NODE *pDst = NULL; /* destination head ptr */
NODE **ppDst = &pDst; /* ptr to head or prev->next */
if(pSrc1 == NULL)
return pSrc2;
if(pSrc2 == NULL)
return pSrc1;
while(1){
if(pSrc2->data < pSrc1->data){ /* if src2 < src1 */
*ppDst = pSrc2;
pSrc2 = *(ppDst = &(pSrc2->next));
if(pSrc2 == NULL){
*ppDst = pSrc1;
break;
}
} else { /* src1 <= src2 */
*ppDst = pSrc1;
pSrc1 = *(ppDst = &(pSrc1->next));
if(pSrc1 == NULL){
*ppDst = pSrc2;
break;
}
}
}
return pDst;
}
std::list::sort
更改为基于迭代器而不是列表,并且还更改为自上而下的归并排序,这需要扫描开销。我最初认为切换到自上而下是必要的,以便与迭代器一起使用,但当再次被问及时,我进行了调查,并确定切换到较慢的自上而下方法是不必要的,可以使用相同的基于迭代器的逻辑实现自下而上。此链接中的答案解释了这一点,并提供了一个独立的示例,以及VS2019中std::list::sort()
的替代品在包含文件"list"中。
基于得票最高的答案,这是一个经过测试、可用的单链表的C++
版本。
singlelinkedlist.h:
#pragma once
#include <stdexcept>
#include <iostream>
#include <initializer_list>
namespace ythlearn{
template<typename T>
class Linkedlist{
public:
class Node{
public:
Node* next;
T elem;
};
Node head;
int _size;
public:
Linkedlist(){
head.next = nullptr;
_size = 0;
}
Linkedlist(std::initializer_list<T> init_list){
head.next = nullptr;
_size = 0;
for(auto s = init_list.begin(); s!=init_list.end(); s++){
push_left(*s);
}
}
int size(){
return _size;
}
bool isEmpty(){
return size() == 0;
}
bool isSorted(){
Node* n_ptr = head.next;
while(n_ptr->next != nullptr){
if(n_ptr->elem > n_ptr->next->elem)
return false;
n_ptr = n_ptr->next;
}
return true;
}
Linkedlist& push_left(T elem){
Node* n = new Node;
n->elem = elem;
n->next = head.next;
head.next = n;
++_size;
return *this;
}
void print(){
Node* loopPtr = head.next;
while(loopPtr != nullptr){
std::cout << loopPtr->elem << " ";
loopPtr = loopPtr->next;
}
std::cout << std::endl;
}
void call_merge(){
head.next = merge_sort(head.next);
}
Node* merge_sort(Node* n){
if(n == nullptr || n->next == nullptr)
return n;
Node* middle = getMiddle(n);
Node* left_head = n;
Node* right_head = middle->next;
middle->next = nullptr;
return merge(merge_sort(left_head), merge_sort(right_head));
}
Node* getMiddle(Node* n){
if(n == nullptr)
return n;
Node* slow, *fast;
slow = fast = n;
while(fast->next != nullptr && fast->next->next != nullptr){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
Node* merge(Node* a, Node* b){
Node dummyHead;
Node* current = &dummyHead;
while(a != nullptr && b != nullptr){
if(a->elem < b->elem){
current->next = a;
a = a->next;
}else{
current->next = b;
b = b->next;
}
current = current->next;
}
current->next = (a == nullptr) ? b : a;
return dummyHead.next;
}
Linkedlist(const Linkedlist&) = delete;
Linkedlist& operator=(const Linkedlist&) const = delete;
~Linkedlist(){
Node* node_to_delete;
Node* ptr = head.next;
while(ptr != nullptr){
node_to_delete = ptr;
ptr = ptr->next;
delete node_to_delete;
}
}
};
}
main.cpp:
#include <iostream>
#include <cassert>
#include "singlelinkedlist.h"
using namespace std;
using namespace ythlearn;
int main(){
Linkedlist<int> l = {3,6,-5,222,495,-129,0};
l.print();
l.call_merge();
l.print();
assert(l.isSorted());
return 0;
}
slow = slow->next;
fast = fast->next->next;
}
为什么我们要检查fast->next->next?谢谢。 - Chandra Shekhar