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

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

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

題目要求咱們在O(n log n)時間複雜度下完成對單鏈表的排序,咱們知道平均時間複雜度爲O(n log n)的排序方法有快速排序、歸併排序和堆排序。而通常是用數組來實現二叉堆,固然能夠用二叉樹來實現,可是這麼作太麻煩,還得花費額外的空間構建二叉樹,因而不採用堆排序。

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

快速排序

在通常實現的快速排序中,咱們經過首尾指針來對元素進行切分,下面採用快排的另外一種方法來對元素進行切分。git

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

圖示以下:
數組

代碼以下:ui

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)。spa

歸併排序的通常步驟爲:指針

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

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

代碼以下:

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/main/java/algorithm/leetcode/L_148_SortList.java

參考文檔

  1. 鏈表排序(冒泡、選擇、插入、快排、歸併、希爾、堆排序)
相關文章
相關標籤/搜索