一开始写链表的排序代码是因为在《算法》里面看到说自低向上的归并算法比较适合链表结构的数据,但是里面没有给出代码,于是就自己拿来练手。
链表排序与数组排序最大的不同就是随机访问代价太高,每次都需要遍历。
归并的思想就是把大的数据分成小数据,先将小数据排序,然后再并起来。
而自低向上的归并,是一变二,二变四...最终变成一个整体(具体请参考算法书)
对于链表来说,两个小链表的合并与两个数组的合并没有太大区别,都是两边同时遍历进行比较然后取较小(大)值,形成一个大的有序序列。
不同是如何将大序列分成小序列,数组可以直接把下表均分,而链表则需要遍历一遍才能均分。
我看到网上的实现,好像也都是这么做的:
用归并排序对链表进行排序
其中的FrontBackSplit()函数就是用快慢指针进行遍历,然后分成2半。
不过每次分完之后链表的长度都会变小,所以其实花费的时间还是可以接受的。
然后我自己写了一个实现方法,是用队列保存各个小序列的头指针,从而避免每次找头指针的消耗。这是一开始看到题目时候的思路,但是现在回过头看,感觉为了节省这点时间额外增加了这么多的空间消耗(N/2长度的队列)似乎有点不值。不过还是把代码贴上来:
两个链表的合并我就直接用了上面那片文章里的函数了,写得非常简洁:
myNode* mergeForList(myNode* a, myNode* b)
{
myNode* result = nullptr;
if (a == nullptr)
return(b);
else if (b==nullptr)
return(a);
/* 使用递归调用的方法 */
if (a->val <= b->val)
{
result = a;
result->next = mergeForList(a->next, b);
}
else
{
result = b;
result->next = mergeForList(a, b->next);
}
return(result);
}
myNode定义:
class myNode
{
public :
int val;
myNode* next;
};
归并排序的实现:
void mergeSort2ForList(myNode* &head)//链表的归并排序
{
if(head==nullptr) return;
queue<myNode*> ptQueue;
myNode *head1, *head2, *head3;
head1=head;
//先两两合并,如1和2合并,3和4合并,合并完把结尾指向空指针,并把头指针加入队列
while(head1!=nullptr&&head1->next!=nullptr)
{
head2=head1->next;
head3=head2->next;
if(head1->val <=head2->val)
{//如果小序列里的第一个节点比较小,那么不需要交换位置,直接把第一个节点作为头指针入队列,第二个节点指向空
ptQueue.push(head1);
head2->next=nullptr;
}
else
{//如果第二个比较小,则交换位置
ptQueue.push(head2);
head2->next=head1;
head1->next=nullptr;
}
head1=head3;
}
if(head1!=nullptr)//说明是单数个数据,把最后一个数据直接入队列
ptQueue.push(head1);
//开始两两合并队列里的序列,注意合并之后的新序列的头指针会继续入队列
while(ptQueue.size()!=1)
{//合并后的长度越来越长,可合并的序列也越来越少
//当合并的序列只有一个时,退出循环
head1=ptQueue.front();//取出第一个序列
ptQueue.pop();
head2=ptQueue.front();//取第二个序列
ptQueue.pop();
ptQueue.push( mergeForList(head1, head2));//合并之后的新序列的头指针继续入队列
}
head=ptQueue.front();//排序后的头指针
}