1. 程式人生 > >Java實現單鏈表的快速排序和歸並排序

Java實現單鏈表的快速排序和歸並排序

解法 nod sin fonts 但是 二叉 復雜 sort tno

本文描述了LeetCode 148題 sort-list 的解法。

題目描述如下:
Sort a linked list in O(n log n) time using constant space complexity.

題目要求我們在O(n log n)時間復雜度下完成對單鏈表的排序,我們知道平均時間復雜度為O(n log n)的排序方法有快速排序、歸並排序和堆排序。而一般是用數組來實現二叉堆,當然可以用二叉樹來實現,但是這麽做太麻煩,還得花費額外的空間構建二叉樹,於是不采用堆排序。

故本文采用快速排序和歸並排序來對單鏈表進行排序。

快速排序

在一般實現的快速排序中,我們通過首尾指針來對元素進行切分,下面采用快排的另一種方法來對元素進行切分。

我們只需要兩個指針p1和p2,這兩個指針均往next方向移動,移動的過程中保持p1之前的key都小於選定的key,p1和p2之間的key都大於選定的key,那麽當p2走到末尾時交換p1與key值便完成了一次切分。

圖示如下:
技術分享圖片

代碼如下:

public ListNode sortList(ListNode head) {
    //采用快速排序
   quickSort(head, null);
   return head;
}
public static void quickSort(ListNode head, ListNode end) {
    if (head != end) {
        ListNode node = partion
(head, end); quickSort(head, node); quickSort(node.next, end); } } public static ListNode partion(ListNode head, ListNode end) { ListNode p1 = head, p2 = head.next; //走到末尾才停 while (p2 != end) { //大於key值時,p1向前走一步,交換p1與p2的值 if (p2.val < head.val) { p1 = p1.next
; int temp = p1.val; p1.val = p2.val; p2.val = temp; } p2 = p2.next; } //當有序時,不交換p1和key值 if (p1 != head) { int temp = p1.val; p1.val = head.val; head.val = temp; } return p1; }

歸並排序

歸並排序應該算是鏈表排序最佳的選擇了,保證了最好和最壞時間復雜度都是nlogn,而且它在數組排序中廣受詬病的空間復雜度在鏈表排序中也從O(n)降到了O(1)。

歸並排序的一般步驟為:

  1. 將待排序數組(鏈表)取中點並一分為二;
  2. 遞歸地對左半部分進行歸並排序;
  3. 遞歸地對右半部分進行歸並排序;
  4. 將兩個半部分進行合並(merge),得到結果。

首先用快慢指針(快慢指針思路,快指針一次走兩步,慢指針一次走一步,快指針在鏈表末尾時,慢指針恰好在鏈表中點)的方法找到鏈表中間節點,然後遞歸的對兩個子鏈表排序,把兩個排好序的子鏈表合並成一條有序的鏈表。

代碼如下:

public ListNode sortList(ListNode head) {
    //采用歸並排序
    if (head == null || head.next == null) {
        return head;
    }
    //獲取中間結點
    ListNode mid = getMid(head);
    ListNode right = mid.next;
    mid.next = null;
    //合並
    return mergeSort(sortList(head), sortList(right));
}

/**
 * 獲取鏈表的中間結點,偶數時取中間第一個
 *
 * @param head
 * @return
 */
private ListNode getMid(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    //快慢指針
    ListNode slow = head, quick = head;
    //快2步,慢一步
    while (quick.next != null && quick.next.next != null) {
        slow = slow.next;
        quick = quick.next.next;
    }
    return slow;
}

/**
 *
 * 歸並兩個有序的鏈表
 *
 * @param head1
 * @param head2
 * @return
 */
private ListNode mergeSort(ListNode head1, ListNode head2) {
    ListNode p1 = head1, p2 = head2, head;
   //得到頭節點的指向
    if (head1.val < head2.val) {
        head = head1;
        p1 = p1.next;
    } else {
        head = head2;
        p2 = p2.next;
    }

    ListNode p = head;
    //比較鏈表中的值
    while (p1 != null && p2 != null) {

        if (p1.val <= p2.val) {
            p.next = p1;
            p1 = p1.next;
            p = p.next;
        } else {
            p.next = p2;
            p2 = p2.next;
            p = p.next;
        }
    }
    //第二條鏈表空了
    if (p1 != null) {
        p.next = p1;
    }
    //第一條鏈表空了
    if (p2 != null) {
        p.next = p2;
    }
    return head;
}

完整代碼放在:
https://github.com/morethink/algorithm/blob/master/src/algorithm/leetcode/SortList.java

參考文檔

  1. 鏈表排序(冒泡、選擇、插入、快排、歸並、希爾、堆排序)

Java實現單鏈表的快速排序和歸並排序