本文描述了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)。
歸並排序的一般步驟為:
- 將待排序數組(鏈表)取中點並一分為二;
- 遞歸地對左半部分進行歸並排序;
- 遞歸地對右半部分進行歸並排序;
- 將兩個半部分進行合並(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;
}
參考文檔: