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

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

 #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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值