前段时间准备一些面试题时,才开始考虑链表的排序问题。当时一想就感觉原地归并的实现有戏,上网找了一下,发现有人竟是先将链表打散成数组再来排,非常汗。于是自己写了个非递归的原地归并实现,如下:
#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),遍历的开销同样无法避免)。
不过我只是一介菜鸟耳,希望听听各位高手的看法。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sgzwkrm/archive/2009/06/02/4235199.aspx