對鏈表進行歸併排序

題幹:對一個鏈表進行歸併排序。

對於數組,歸併排序的思想是先將數據分成兩部份,分別排序。然後再對已經排好序的兩個數組歸併。其中在對兩部份數組排序時進行遞歸,最後得到的即是排好序的數組。

對於鏈表,歸併的思想也是如此。需要解決的問題有兩個,第一個是如何將鏈表平均分成兩部份。 第二個是將兩個有序鏈表合併成一個有序鏈表。 解決了這兩個問題,歸併排序鏈表就迎仞而解了。

對於第一個問題,可以採用快慢指針的方式,找到鏈表的中點,然後將節點斷開,形成兩個鏈表。
代碼如下:

/**
 * 返回中間節點
 * @param head
 * @return
 */
public ListNode split(ListNode head) {
    if (head == null) {
        return head;
    }

    ListNode prev = null;
    ListNode slow = head;
    ListNode fast = head;
  	// 注意這裏了要判斷fast.next 是否爲空
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        prev = slow;
        slow = slow.next;
    }
    prev.next = null;   // 斷開鏈表,返回後面一段的第一個節點

    return slow;
}

對於第二個問題,即用兩個指針分別指向兩個鏈表,然後比較兩個指針指向的鏈表節點大小,將小的節點添加到結果中去,此時將指向節點值小的指針向後移動,同時保持另一個指針不動,直到其中一個鏈表遍歷完成。最後,將還沒有遍歷完成的鏈表鏈接到結果中。
代碼如下:

/**
 * 合併兩個有序鏈表
 * @param left
 * @param right
 * @return
 */
public ListNode merge(ListNode left, ListNode right) {
    ListNode head = new ListNode(-1);
    ListNode cur = head;
    while (left != null && right != null) {
        if (left.val > right.val) {
            cur.next = right;
            right = right.next;
        } else {
            cur.next = left;
            left = left.next;
        }
        cur = cur.next;
    }

    while (left != null) {
        cur.next = left;
        cur = cur.next;
        left = left.next;
    }

    while (right != null) {
        cur.next = right;
        cur = cur.next;
        right = right.next;
    }


    return head.next;
}

有了這兩個問題的基礎,就可以很容易的進行歸併排序了。
代碼如下:

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) {
        // 爲空 或 只有一個鏈表節點均不用排序
        return head;
    }
    ListNode mid = split(head);
    ListNode left = sortList(head);
    ListNode right = sortList(mid);
    return merge(left, right);
}

初看這個問題的時候,感覺會無從下手,但是一步步拆解問題後都是可以通過已有的知識來解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章