归并排序是效率非常高的一种排序方式,和快速排序一样用了分治的思想。分治法的精髓在于将一个复杂问题分割成为多个简单的子问题,然后将子问题逐个解决,最终合并在一起以后就是复杂问题的解了。
这篇文章主要讲归并排序链表的实现方式,如果想了解数组的归并排序,可移步这里。
归并排序的思想其实挺简单的,简而言之就是将数列分割成子序列,先对子序列进行排序,再将排好序的子序列合并成一个完整的数列。具体例子如下:
[3 7 6 4 8 9 2 1]
/ \
分割 [3 7 6 4] [8 9 2 1]
/ \ / \
分割 [3 7] [6 4] [8 9] [2 1]
/ \ / \ / \ / \
分割 [3] [7] [6] [4] [8] [9] [2] [1]
\ / \ / \ / \ /
合并 [3 7] [4 6] [8 9] [1 2]
\ / \ /
合并 [3 4 6 7] [1 2 8 9]
\ /
合并 [1 2 3 4 6 7 8 9]
分割时从数列中间开始,将数列分成两部分,然后对分割后的序列继续进行分割直到分割不能再分为止。然后对已经排好序的子序列进行合并,重新产生一个排序好的数列。
用链表进行归并排序和数组排序略有不同,但是思想还是一样的。唯一的难点在于如何找到链表的中间点,这里就要运用到了一个额外的技巧——快慢指针。快慢指针常常用于判断链表中是否有环,简单来说就是有两个指针,快指针每次走两个节点,慢指针每次走一个节点,当快指针走到链表尽头时,慢指针才走到了链表的一半,此时就是我们想要的中间节点。
看代码:
//链表结构单位
struct Node{
int val;
Node* next;
};
//node -- 链表表头
Node* MergeSort(Node* node){
//先判断链表长度是否大于1,小于1时无须排序
if(node!=NULL&&node->next!=NULL){
//运用快慢指针,找到链表的中间节点
Node *fast=node->next;
Node *slow=node;
while(fast!=NULL&&fast->next!=NULL){
fast=fast->next->next;
slow=slow->next;
}
//将链表分成两部分进行分割
Node *p1=MergeSort(slow->next);
slow->next=NULL; //这儿很重要,仔细想想为什么
Node *p2=MergeSort(node);
//对两条子链进行归并
Node *p0=(Node *)malloc(sizeof(Node));
Node *p=p0;
while(p1!=NULL&&p2!=NULL){
if(p1->val<p2->val){
p->next=p1;
p1=p1->next;
}else{
p->next=p2;
p2=p2->next;
}
p=p->next;
}
if(p1!=NULL){
p->next=p1;
}
if(p2!=NULL){
p->next=p2;
}
p=p0->next;
free(p0);
return p;
}
return node;
}