归并排序是分治算法思想的一个具体体现。利用分治算法进行排序时,可以从两个方面进行考虑:
- merge方法:将两个已经有序的小集合合并为一个有序的大集合;
- sort方法:利用递归的方法,将总集合逐次二分为只有单个元素的集合,然后用merge方法将集合逐次两两合并,最后达到将总集合排序的目的。
对于链表而言,由于不能像数组一样随机访问,故而使用一般方法会因为迭代寻找节点而消耗大量的时间。归并排序可以说是链表排序的最佳方法,一方面利用分治算法不用大量的寻找节点从而节省了时间,另一方面不用开辟额外的空间保存节点。
Java代码如下所示:
import java.util.Iterator;
// 可以调用递归排序的链表
public class MergeLinkList<T extends Comparable<T>> implements Iterable<T>{
// 定义节点
private class Node {
T item;
Node next;
}
private Node first;// 头节点
private int currentLength;
public MergeLinkList() {
first = null;
currentLength = 0;
}
public boolean isEmpty() {
return first == null;
}
public int size() {
return currentLength;
}
public void addFirst(T item) {
Node newNode = new Node();
newNode.item = item;
newNode.next = first;
first = newNode;
currentLength++;
}
public T removeFirst() {
if (isEmpty()) {
throw new NullPointerException();
} else {
T item = first.item;
first = first.next;
currentLength--;
return item;
}
}
@SuppressWarnings("unchecked")
public static <T> boolean less(Comparable<T> v,Comparable<T> w) {
return v.compareTo((T)w)<0;
}
// 将两个有序链表合为一个有序链表并返回
private Node merge(Node a,Node b) {
if (a==null&&b==null) {
return null;
}
Node head=new Node();// 创建一个临时头节点
Node t=head;// 链表插入节点
while (a!=null&&b!=null) {
if (less(a.item, b.item)) {
// a的头节点插入
t.next=a;
t=a;
a=a.next;
}else {
// b的头节点插入
t.next=b;
t=b;
b=b.next;
}
}
// 剩余的节点全部插入
if (a!=null) {t.next=a;}
if (b!=null) {t.next=b;}
t=head.next;// 跳过头节点
head=null;// 删除头节点
return t;
}
// 递归的将链表先二分,后归并
public Node sort(Node head) {
if (head==null||head.next==null) {
return head;
}
Node pfast=head.next;// 快指针,到达链表尾部时慢指针刚好指向链表中部
Node pslow=head;// 慢指针,配合快指针记录链表的中部
while (pfast.next!=null&&pfast.next.next!=null) {
pslow=pslow.next;
pfast=pfast.next.next;
}// 结束时pslow指向中间,pfast指向末尾
pfast=sort(pslow.next);// 后半部分排序
pslow.next=null;// 从中间断开为两个链表
pslow=sort(head);// 前半部分排序
return merge(pslow, pfast);// 将前后有序链表合并
}
public void sort() {
Node newFirst=sort(first);
first=newFirst;
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private Node current=first;
@Override
public boolean hasNext() {
return current!=null;
}
@Override
public T next() {
T item=current.item;
current=current.next;
return item;
}
};
}
public static void main(String[] args) {
MergeLinkList<String> list=new MergeLinkList<>();
String[] s="uitvxbmqkcywhaszofnpldrgje".split("");
for (int i = 0; i < s.length; i++) {
list.addFirst(s[i]);
}
for (String string : list) {
System.out.print(string+" ");
}
System.out.println();
list.sort();
for (String string : list) {
System.out.print(string+" ");
}
}
}
代码中利用快慢指针进行二分的方法非常巧妙,快指针的移动是慢指针的两倍,所以快指针到达链表尾部时慢指针刚好指向链表中部。此时快指针的使命完成,我们可以让它指向排序好的后半链表。将链表从中部断开,此时慢指针也完成了使命,我们让它指向排序好的前半链表。最后调用merge方法将两者合并。