上篇博客中我们回顾了数组的几大常用排序算法,这里结合链表的两道题目,我们将会实现链表的插入排序、归并排序和快速排序。首先看一下147插入排序这道题目,插入排序,顾名思义就是挨个遍历链表中的元素,然后将其插入到前面已经排序好的链表的相应位置,保持前面的链表仍然是排序链表即可。那么按照这个思路我们可以写出下面的代码:
public static ListNode insertionSortList(ListNode head){
if(head == null)
return head;
//定义一个新的链表用于存储排序好的链表
ListNode newHead = new ListNode(0);
//cur用于遍历要排序的链表,pre指向排序号的链表,next暂存剩下的链表
ListNode cur=head, pre=newHead, next;
while(cur != null){
//保存cur后面的链表
next = cur.next;
//一=般都选择在当前元素后面插入,因为这样比较方便,也不会出现边界异常的现象
while(pre.next != null && pre.next.val < cur.val)
pre = pre.next;
//插入到相应位置
cur.next = pre.next;
pre.next = cur;
//恢复pre和cur的值
pre = newHead;
cur = next;
}
return newHead.next;
}
上面这种方法简单直接,但是由于每次插入的时候都要从第一个元素开始遍历,所以会导致算法的效率比较低,那么应该如何改进呢?我们可以考虑一下,在上一次插入之后不把pre重定向到newHead,而是就留在刚才插入的位置,这样当下一个元素到来时直接从该位置开始遍历,如果pre的值比较大,那么久返回newHead重新遍历,如果pre的值小于cur,那就直接向后遍历,这样就省去了从头到pre的遍历时间,可以大大的提升代码效率。这样可以击败98%的用户,代码入下:
public static ListNode insertionSortList2(ListNode head){
if(head == null)
return head;
ListNode newHead = new ListNode(0);
ListNode cur=head, pre=newHead, next;
while(cur != null){
next = cur.next;
if(pre != newHead && pre.val > cur.val)
pre = newHead;
while(pre.next != null && pre.next.val < cur.val)
pre = pre.next;
cur.next = pre.next;
pre.next = cur;
cur = next;
}
return newHead.next;
}
此外,还有一种思路就是针对插入位置是链表的头和尾时分别考虑,上面的方法使用新建一个newHead和pre.next.val的方法避免了这两种边界情况。那么我们也可以分别考虑然后进行插入操作,代码入下:
public ListNode insertionSortList1(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode sortedHead = head, sortedTail = head;
head = head.next;
sortedHead.next = null;
while (head != null)
{
ListNode temp = head;
head = head.next;
temp.next = null;
// new val is less than the head, just insert in the front
if (temp.val <= sortedHead.val)
{
temp.next = sortedHead;
sortedTail = sortedHead.next == null ? sortedHead : sortedTail;
sortedHead = temp;
}
// new val is greater than the tail, just insert at the back
else if (temp.val >= sortedTail.val)
{
sortedTail.next = temp;
sortedTail = sortedTail.next;
}
// new val is somewhere in the middle, we will have to find its proper
// location.
else
{
ListNode current = sortedHead;
while (current.next != null && current.next.val < temp.val)
{
current = current.next;
}
temp.next = current.next;
current.next = temp;
}
}
return sortedHead;
}
上面就是链表直接插入排序法,还是比较简单的,那么接下来让我们看看148题,也就是使用归并排序和快速排序的方法实现链表排序吧。
首先看一下归并排序,其思路是先将所有元素两两划分为一组,然后递归合并,合并就相当于将两个排序好的链表合并为一个。这样最终我们就可以将链表排序好了。那么关键所在就是先分裂,在合并。代码如下所示:
public ListNode sortList(ListNode head) {
if (head == null || head.next == null)
return head;
// 使用两个指针,一快一慢,当快指针走到链表尾部时,慢指针正好到中间位置。
ListNode prev = null, slow = head, fast = head;
while (fast != null && fast.next != null) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
//prev表示第一个链表的尾部,所以指向空。slow为第二个指针的头部
prev.next = null;
// 继续分裂
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
// 当分裂到最后每个元素时,开始两两合并
return merge(l1, l2);
}
//将两个有序链表合并的函数
ListNode merge(ListNode l1, ListNode l2) {
ListNode l = new ListNode(0), p = l;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
p.next = l1;
l1 = l1.next;
} else {
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
if (l1 != null)
p.next = l1;
if (l2 != null)
p.next = l2;
return l.next;
}
另外一种方法就是快速排序法,快速排序的思路就是先将链表根据某个基础元素值分为两部分,比他大的和比他小的,然后一直这样分类下去,最后在进行合并即可,代码如下所示:
ListNode sortList(final ListNode h){
if(h == null || h.next == null)
return h;
/*split into three list*/
ListNode fakesmall = new ListNode(0), small = fakesmall;
ListNode fakelarge = new ListNode(0), large = fakelarge;
ListNode fakeequal = new ListNode(0), equal = fakeequal;
ListNode cur = h; // pivot is h.
while(cur != null){
if(cur.val < h.val){
small.next = cur;
small = small.next;
}
else if(cur.val == h.val){
equal.next = cur;
equal = equal.next;
}
else{
large.next = cur;
large = large.next;
}
cur = cur.next;
}
// put an end.
small.next = equal.next = large.next = null;
// merge them and return . merge reusing below one. merge for quicksort should be simplified.
return merge(merge(sortList(fakesmall.next), sortList(fakelarge.next)),fakeequal.next) ;
}
ListNode merge(ListNode l1, ListNode l2) {
ListNode l = new ListNode(0), p = l;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
p.next = l1;
l1 = l1.next;
} else {
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
if (l1 != null)
p.next = l1;
if (l2 != null)
p.next = l2;
return l.next;
}