基于链表的二路归并排序和基于数组的二路归并排序的总体实现思想是一样的,同样是基于分治法。在递归算法结构下体现的是切分(相对比较常见的切分方法是对半切分),到分无可分时,切分函数调用逐个退栈继而进行合并函数调用,在进行合并的过程中进行一个类似于选择排序算法的思想实现。
思想如图:(图片来源:https://www.cnblogs.com/horizonice/p/4102553.html)
其中在基于链表实现的算法下进行对半切分时,涉及到一个链表对切中点的寻找过程,在此采用两个不同的跳跃步长以寻找链表中间节点。代码如:
//跳跃两个位置节点(pHead:后置节点,step=1(跳转节点数);qHead:前置节点,step=2),
//当本节点跳转到链表末尾时,后置节点(pHead)刚好处在链表中间的下一个节点上,前置节点出在链表末尾
qHead=qHead->next->next;
//记录后置跳转节点,当本循环退出时,mid记录的便是链表中间节点(前置节点链表的最后一个节点)
mid=pHead;
//跳跃一个位置节点
pHead=pHead->next;
另外一个便是链表合并过程了,在此不再赘述,整个算法思想及具体细节详见代码。
完整代码如下(这可能是我今年写过最详细的中文代码注释了)
#include "stdafx.h";
#include<stdio.h>
#include <iostream>
using namespace std;
class ListNode{
public:
int data; //保存节点的值
ListNode *next; //指向下一个节点
ListNode(int c){
this->data=c;
}
};
class BiSortUtil {
public:
ListNode* sortList(ListNode* head) {
if(head==NULL||head->next==NULL)
return head;
return splitList(head);
}
/*
不断进行二路切分直到head仅有一个元素的时候,进入merge进行合并排序,
总体思想即为先分后和,在合并的时候进行排序操作
*/
ListNode* splitList(ListNode*head)
{
//单元素直接返回(切到没有元素可以切的时候)
if(head->next==NULL)
{
return head;
}
//记录寻找链表中间节点时的指针跳转,主要在于获取链表中间节点
ListNode* pHead,*qHead,*mid;
pHead=head;
qHead=head;
mid=NULL;
//获取到链表的中间节点,通过1、2两个不同的跳跃距离获取链表中间节点
while(qHead!=NULL&&qHead->next!=NULL)
{
//跳跃两个位置节点(pHead:后置节点,step=1(跳转节点数);qHead:前置节点,step=2),
//当本节点跳转到链表末尾时,后置节点(pHead)刚好处在链表中间的下一个节点上,前置节点出在链表末尾
qHead=qHead->next->next;
//记录后置跳转节点,当本循环退出时,mid记录的便是链表中间节点(前置节点链表的最后一个节点)
mid=pHead;
//跳跃一个位置节点
pHead=pHead->next;
}
//截断链表,划分为两个子链表,
//此时分别获得以head、pHead为头结点的链表针对于原head链表的切分后子链表(也就是head链表节点+pHead链表节点=原head节点)
mid->next=NULL;
ListNode *left,*right;
//递归切分得到左边节点,实际上在进行切分时:
//第一次获得返回的时候即是链表切无可切的时候,下一步便是合并排序开始的时候
left=splitList(head);
//递归切分得到右边节点
right=splitList(pHead);
//进入合并排序
return mergeSort(left,right);
}
/*
对链表进行合并
*/
ListNode* mergeSort(ListNode *left,ListNode*right)
{
//暂存临时头结点
ListNode *pRes=new ListNode(0);
ListNode *tail=pRes;
//对两个各自有序的子链表进行合并操作,在此借助链表的尾插法进行比较链接,不需要开辟额外空间
while(left!=NULL&&right!=NULL)
{
//针对具体值进行比较,按非严格升序排序
if(left->data<=right->data)
{
//比较值更小或相等的节点先链接到tail节点上,注意这里在比较值相等的情况下优先链接left表
tail->next=left;
left=left->next;
}
else
{
tail->next=right;
right=right->next;
}
//此处的链接采用链表尾插法思想,即在此tail始终指向最后一个节点
tail=tail->next;
}
//对未完成链接的链表进行链接
if(left!=NULL)
tail->next=left;
if(right!=NULL)
tail->next=right;
//头结点仅作为链接使用,保存的是无效数据,在此进行丢弃回收,在此对tail指针进行复位到第一个有效数据节点
tail=pRes->next;
delete pRes;
//返回合并后的链表头结点(头结点持有有效数据,注意tail指向已重置)
return tail;
}
//遍历输出链表
void trave_list(ListNode *head)
{
if(head->next==NULL)
{
cout<<"空表!"<<endl;
return;
}
cout<<"遍历结果如下:"<<endl;
ListNode *show=head->next;
cout<<"head->";
while(show)
{
cout<<(show->data)<<"->";
show=show->next;
}
cout<<"NULL"<<endl;
}
};
int main(int argc, char* argv[])
{
BiSortUtil *su=new BiSortUtil();
int arr[]={3,1,9,6,2,2,0};
ListNode *ph=new ListNode(0);
ListNode *t=ph;
//数组转链表
for(int i=0;i<sizeof(arr) / sizeof(arr[0]);i++){
//cout<<cs[i]<<endl;
ListNode *t=new ListNode(arr[i]);
ph->next=t;
ph=ph->next;
}
ph->next=NULL;
ph=t;
cout<<"二分排序前"<<endl;
su->trave_list(ph);
su->sortList(ph);
cout<<"二分排序后"<<endl;
su->trave_list(ph);
return 0;
}
运行结果如下