題幹:對一個鏈表進行歸併排序。
對於數組,歸併排序的思想是先將數據分成兩部份,分別排序。然後再對已經排好序的兩個數組歸併。其中在對兩部份數組排序時進行遞歸,最後得到的即是排好序的數組。
對於鏈表,歸併的思想也是如此。需要解決的問題有兩個,第一個是如何將鏈表平均分成兩部份。 第二個是將兩個有序鏈表合併成一個有序鏈表。 解決了這兩個問題,歸併排序鏈表就迎仞而解了。
對於第一個問題,可以採用快慢指針的方式,找到鏈表的中點,然後將節點斷開,形成兩個鏈表。
代碼如下:
/**
* 返回中間節點
* @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);
}
初看這個問題的時候,感覺會無從下手,但是一步步拆解問題後都是可以通過已有的知識來解決。