单向链表的原地归并排序实现

本文介绍了一种非递归的链表原地归并排序算法,并提供了完整的C++实现代码。该算法通过不断合并相邻的链表块来达到排序的目的,最终实现了O(n log n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前段时间准备一些面试题时,才开始考虑链表的排序问题。当时一想就感觉原地归并的实现有戏,上网找了一下,发现有人竟是先将链表打散成数组再来排,非常汗。于是自己写了个非递归的原地归并实现,如下:

#include <stdlib.h>

template<class T>
struct Node
{
Node(T d)
{
data = d;
next = NULL;
}
T data;
Node *next;
};

template<class T>
Node<T> * GetTailNode( Node<T> *startNode, int length )
//按长度找到尾节点
{
while(length-- && startNode->next != NULL)
{
startNode = startNode->next;
}
return startNode;
}

template<class T, class CmpFunc>
Node<T> * Merge( Node<T> *&startNode, int currBlockLen, CmpFunc cmp )
//从startNode开始,按currBlockLen二分,进行归并操作
{
bool canMerge = true;
Node<int> *leftBlock = startNode, *rightBlock = startNode;

// 找到rightBlock的头节点
for(int i = 0; i<currBlockLen; ++i)
{
rightBlock = rightBlock->next;
if(rightBlock == NULL)
{
canMerge = false;
break;
}
}

if(!canMerge)
return NULL;

Node<int> *currSortedNode;//排序结果的当前节点
Node<int> *currTailNode = NULL;//排序结果的尾节点,用于最后将排序结果与下一段链表数据连接
int leftCount = 0, rightCount = 0;

//初始化currSortedNode
if( cmp(leftBlock->data, rightBlock->data) )
{
++rightCount;
currSortedNode = startNode = rightBlock;
rightBlock = rightBlock ->next;
}
else
{
++leftCount;
currSortedNode = leftBlock;
leftBlock = leftBlock ->next;
}

while(true)
{
if(leftCount == currBlockLen || leftBlock == NULL )
//当leftBlock完成排序时,leftBlock尾节点即为currSortedNode
{
currSortedNode->next = rightBlock;//剩下的rightBlock无需再排序,直接加入结果链
currTailNode = GetTailNode( currSortedNode, currBlockLen-rightCount );//找到结果的尾节点
break;
}
else if(rightCount == currBlockLen || rightBlock == NULL )
//当右Block完成排序,此时右Block的尾节点即为currSortedNode
{
Node<T> *tmpNode = currSortedNode->next;//记下下一段链表的头节点

currSortedNode->next = leftBlock;
currTailNode = GetTailNode( currSortedNode, currBlockLen-leftCount );
currTailNode->next = tmpNode;//将结果链表与下一段链表连起来
break;
}
else
//普通的归并操作
{
if(cmp(leftBlock->data, rightBlock->data))
{
++rightCount;
currSortedNode->next = rightBlock;
rightBlock = rightBlock->next;
}
else
{
++leftCount;
currSortedNode->next = leftBlock;
leftBlock = leftBlock->next;
}
currSortedNode = currSortedNode->next;
}
}
return currTailNode->next != NULL ? currTailNode : NULL;//返回NULL意味着一轮归并的结束
}

template<class T, class CmpFunc>
void MergeSort( Node<T>* &dataLink, CmpFunc cmp )
{
int currBlockLen = 1;
Node<T> *lastTailNode;
while( true )
{
lastTailNode = Merge( dataLink, currBlockLen, cmp );
if(lastTailNode != NULL)
{
do
{
lastTailNode = Merge( lastTailNode->next, currBlockLen, cmp );
}
while ( lastTailNode != NULL );
currBlockLen *= 2;
}
else
break;
}
}

对以上实现的测试代码如下:

Node<int> *BuildLink( int *data, int len )
{
Node<int> *header = new Node<int>(data[0]);
Node<int> *currNode = header;
for(int i = 1; i<len; ++i)
{
currNode->next = new Node<int>( data[i] );
currNode = currNode->next;
}
return header;
}

inline bool IntCmp( int &a, int &b )
{
return a>b;
}

#include <time.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
int main()
{
srand(time(0));
int dataLen = 10000;
int *data = new int[dataLen];
for(int i = 0; i<dataLen; ++i)
{
data[i] = rand();
}
Node<int> *link = BuildLink(data,dataLen);
MergeSort(link, IntCmp);

while(link != NULL)
{
printf("%d ", link->data);
link = link->next;
}
}

做了这个实现后,我也问了一些人这样是否是最高效的了,结果一ACM大牛坚持认为快排才是最高效的,认为快排的均摊就是好于原地归并。这个我是不同意的,因为我还没想到单向链表上快排的高效实现(一个交换操作就很够呛了),而且即使实现了,均摊也几乎没可能超过这里的原地归并实现(这里最差就O(nlgn),系数大不到哪去,就是遍历链表的开销,而快排最优下也是O(nlgn),遍历的开销同样无法避免)。

不过我只是一介菜鸟耳,希望听听各位高手的看法。

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/sgzwkrm/archive/2009/06/02/4235199.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值