單鏈表排序(快速排序、歸併排序)
阿新 • • 發佈:2018-12-31
本題目來源於LeetCode,具體如下:
Sort a linked list in O(n log n) time using constant space complexity.
題目要求複雜度O(nlogn),因此我們很自然考慮使用快速排序或者歸併排序,但是後來經過實踐證明,使用快速排序總是AC超時,歸併排序則可以正確AC。
分析一下原因,個人認為是與測試資料有關,因為快速排序不能保證演算法複雜度一定是O(nlogn),當資料比較集中時,即使做隨機選取key值,演算法的複雜度也非常接近O(N^2),因此會出現超時,所以考慮使用歸併排序。
下面是採用歸併排序的思路已經AC程式碼:
主要考察
#include <iostream> #include <string> #include <algorithm> #include <stack> #include <vector> #include <fstream> using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class Solution { public: ListNode* mergeLists(ListNode *a, ListNode *b) //合併兩個已經排序的連結串列 { if (a == NULL) return b ; if (b == NULL) return a ; ListNode *ret = NULL ; ListNode *tail = NULL ; ret = new ListNode(-1) ; tail = ret ; while (a && b) if (a->val < b->val) { tail->next = a ; tail = tail->next ; a = a->next ; } else { tail->next = b ; tail = tail->next ; b = b->next ; } if (a) tail->next = a ; if (b) tail->next = b ; ListNode *del = ret ; ret = ret->next ; delete del ; return ret ; } ListNode *getMid(ListNode *head) //得到中間節點 { if (!head) return NULL ; if (!head->next) return head ; ListNode *slow = head ; ListNode *fast = head->next ; while (fast && fast->next) { slow = slow->next ; fast = fast->next->next ; } return slow ; } ListNode *sortList(ListNode *head) { //合併排序 if (!head) return NULL ; if (!head->next) return head ; ListNode *mid = getMid(head) ; ListNode *nextPart = NULL ; if (mid) { nextPart = mid->next ; mid->next = NULL ; } return mergeLists( sortList(head) , sortList(nextPart) ) ; } }; void insertBack(ListNode** head, ListNode** tail, ListNode* n) //從尾部插入 { if (n) { if (*head == NULL) { *head = n ; *tail = n ; } else { (*tail)->next = n ; *tail = n ; } } } int main(int argc, char** argv) { ifstream in("data.txt") ; ListNode* head = NULL ; ListNode* tail = NULL ; int val ; Solution s ; while(in >> val) { ListNode*tmp = new ListNode(val) ; insertBack(&head, &tail, tmp) ; } head = s.sortList(head) ; while(head) { cout << head->val << " " ; head = head->next ; } cout << endl ; return 0 ; }
下面再說一下自己AC超時的程式碼吧,
這裡我嘗試了兩種實現方案:
第一種是:
在找劃分點的過程中,維護連個連結串列Left 和Right 所有不大於key的元素都鏈到Left上,大於key的鏈到Right上,最後再將Left, key , Right三部分連線起來。
程式碼如下:
#include <iostream> #include <string> #include <algorithm> #include <stack> #include <vector> #include <fstream> using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class Solution { public: inline void insertBack(ListNode** head, ListNode** tail, ListNode* n) //從尾部插入 { if (n) { if (*head == NULL) { *head = n ; *tail = n ; } else { (*tail)->next = n ; *tail = n ; } } } ListNode *sortList(ListNode *head) { if (!head) return NULL ; if (head->next == NULL) return head ; //劃分 ListNode *tmpNode = head ; head = head->next ; ListNode *sleft = NULL , *eleft = NULL ; ListNode *sright = NULL , *eright = NULL ; while (head) { ListNode *insNode = head ; head = head->next ; insNode->next = NULL ; if (insNode->val > tmpNode->val) insertBack(&sright, &eright, insNode) ; else insertBack(&sleft, &eleft, insNode) ; } //遞迴呼叫 sleft = sortList(sleft) ; sright = sortList(sright) ; //下面三句話第一次沒有加上,除錯了一下午才找到原因 eleft = sleft ; if (eleft) { while(eleft->next) eleft = eleft->next ; } //拼接起來 if (eleft) { head = sleft ; eleft->next = tmpNode ; } else head = tmpNode ; tmpNode->next = sright ; //連線起來 //返回結果 return head ; } }; int main(int argc, char** argv) { ifstream in("data.txt") ; ListNode* head = NULL ; ListNode* tail = NULL ; int val ; Solution s ; while(in >> val) { ListNode*tmp = new ListNode(val) ; s.insertBack(&head, &tail, tmp) ; } head = s.sortList(head) ; while(head) { cout << head->val << " " ; head = head->next ; } cout << endl ; return 0 ; }
第二種方案:使用快排的另一種思路來解答。我們只需要兩個指標p和q,這兩個指標均往next方向移動,移動的過程中保持p之前的key都小於選定的key,p和q之間的key都大於選定的key,那麼當q走到末尾的時候便完成了一次劃分點的尋找。如下圖所示:
實現程式碼如下:
#include <iostream>
#include <string>
#include <algorithm>
#include <stack>
#include <vector>
#include <fstream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* getPartation(ListNode *start, ListNode *end)
{
if (start == end) return start ;
ListNode *p1 = start ;
ListNode *p2 = p1->next ;
int key = start->val ;
while(p2 != end)
{
if (p2->val < key)
{
p1 = p1->next ;
swap(p1->val, p2->val) ; //找到一個比key小的數字,與p1到p2間的數交換,
} //這之間的數都大於等於key
p2 = p2->next ;
}
swap(start->val, p1->val) ; //找到劃分位置
return p1 ;
} ;
void QuickSort(ListNode* start, ListNode *end)
{
if (start != end)
{
ListNode *pt = getPartation(start, end) ;
QuickSort(start, pt) ;
QuickSort(pt->next, end) ;
}
}
ListNode *sortList(ListNode *head) {
QuickSort(head, NULL) ;
return head ;
}
};
void insertBack(ListNode** head, ListNode** tail, ListNode* n) //從尾部插入
{
if (n)
{
if (*head == NULL)
{
*head = n ;
*tail = n ;
}
else
{
(*tail)->next = n ;
*tail = n ;
}
}
}
int main(int argc, char** argv)
{
ifstream in("data.txt") ;
ListNode* head = NULL ;
ListNode* tail = NULL ;
int val ;
Solution s ;
while(in >> val)
{
ListNode*tmp = new ListNode(val) ;
insertBack(&head, &tail, tmp) ;
}
head = s.sortList(head) ;
while(head)
{
cout << head->val << " " ;
head = head->next ;
}
cout << endl ;
return 0 ;
}
雖然使用快速排序的兩種方案都因為超時不能AC,但是練習一下還是很有幫助的。
如果大家發現那裡不對的地方還請批評指正,大家共同學習進步!先行謝過!