单链表的排序(归并和快排)

来源:互联网 发布:ip网络电话交换机 编辑:程序博客网 时间:2024/05/15 14:00

本题目来源于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代码:

主要考察3个知识点,
知识点1:归并排序的整体思想
知识点2:找到一个链表的中间节点的方法
知识点3:合并两个已排好序的链表为一个新的有序链表

归并排序的基本思想是:找到链表的middle节点,然后递归对前半部分和后半部分分别进行归并排序,最后对两个以排好序的链表进行Merge。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <string>  
  3. #include <algorithm>  
  4. #include <stack>  
  5. #include <vector>  
  6. #include <fstream>  
  7. using namespace std;  
  8.   
  9. struct ListNode {  
  10.     int val;  
  11.     ListNode *next;  
  12.     ListNode(int x) : val(x), next(NULL) {}  
  13. };  
  14.   
  15. class Solution {  
  16. public:  
  17.   
  18.     ListNode* mergeLists(ListNode *a, ListNode *b) //合并两个已经排序的链表  
  19.     {  
  20.         if (a == NULL) return b ;  
  21.         if (b == NULL) return a ;  
  22.         ListNode *ret = NULL ;  
  23.         ListNode *tail = NULL ;  
  24.           
  25.         ret = new ListNode(-1) ;  
  26.         tail = ret ;  
  27.         while (a && b)  
  28.             if (a->val < b->val)  
  29.             {  
  30.                 tail->next = a ;  
  31.                 tail = tail->next ;  
  32.                 a = a->next ;  
  33.             }  
  34.             else  
  35.             {  
  36.                 tail->next = b ;  
  37.                 tail = tail->next ;  
  38.                 b = b->next ;  
  39.             }  
  40.         if (a)  
  41.             tail->next = a ;  
  42.         if (b)  
  43.             tail->next = b ;  
  44.   
  45.         ListNode *del = ret ;  
  46.         ret = ret->next ;  
  47.         delete del ;  
  48.   
  49.         return ret ;  
  50.     }  
  51.   
  52.     ListNode *getMid(ListNode *head) //得到中间节点  
  53.     {   
  54.         if (!head) return NULL ;  
  55.         if (!head->next) return head ;  
  56.   
  57.         ListNode *slow = head ;  
  58.         ListNode *fast = head->next ;  
  59.   
  60.         while (fast && fast->next)  
  61.         {  
  62.             slow = slow->next ;  
  63.             fast = fast->next->next ;  
  64.         }  
  65.         return slow ;  
  66.     }  
  67.   
  68.     ListNode *sortList(ListNode *head) { //合并排序  
  69.   
  70.         if (!head) return NULL ;  
  71.         if (!head->next) return head ;  
  72.   
  73.         ListNode *mid = getMid(head) ;  
  74.         ListNode *nextPart = NULL ;  
  75.         if (mid)  
  76.         {  
  77.             nextPart = mid->next ;  
  78.             mid->next = NULL ;  
  79.         }  
  80.   
  81.         return mergeLists(  
  82.              sortList(head) ,  
  83.              sortList(nextPart)   
  84.             ) ;  
  85.     }  
  86. };  
  87.   
  88. void insertBack(ListNode** head, ListNode** tail,  ListNode* n) //从尾部插入  
  89. {     
  90.     if (n)  
  91.     {  
  92.         if (*head == NULL)  
  93.         {  
  94.             *head = n ;  
  95.             *tail = n ;  
  96.         }  
  97.         else  
  98.         {  
  99.             (*tail)->next = n ;  
  100.             *tail = n ;  
  101.         }  
  102.     }  
  103. }  
  104.   
  105. int main(int argc, char** argv)  
  106. {  
  107.   
  108.     ifstream in("data.txt") ;  
  109.     ListNode* head = NULL ;  
  110.     ListNode* tail = NULL ;  
  111.     int val ;  
  112.   
  113.     Solution s ;  
  114.     while(in >> val)  
  115.     {  
  116.         ListNode*tmp = new ListNode(val) ;  
  117.         insertBack(&head, &tail, tmp) ;  
  118.     }  
  119.     head = s.sortList(head) ;  
  120.     while(head)  
  121.     {  
  122.         cout << head->val << " " ;  
  123.         head = head->next ;  
  124.     }  
  125.     cout << endl ;  
  126.     return 0 ;  
  127. }  

下面再说一下自己AC超时的代码吧,

这里我尝试了两种实现方案:

第一种是:

在找划分点的过程中,维护连个链表Left 和Right 所有不大于key的元素都链到Left上,大于key的链到Right上,最后再将Left, key , Right三部分连接起来。

代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <string>  
  3. #include <algorithm>  
  4. #include <stack>  
  5. #include <vector>  
  6. #include <fstream>  
  7. using namespace std;  
  8.   
  9. struct ListNode {  
  10.   int val;  
  11.   ListNode *next;  
  12.   ListNode(int x) : val(x), next(NULL) {}  
  13. };  
  14.   
  15. class Solution {  
  16. public:  
  17.     inline void insertBack(ListNode** head, ListNode** tail,  ListNode* n) //从尾部插入  
  18.     {     
  19.         if (n)  
  20.         {  
  21.             if (*head == NULL)  
  22.             {  
  23.                 *head = n ;  
  24.                 *tail = n ;  
  25.             }  
  26.             else  
  27.             {  
  28.                 (*tail)->next = n ;  
  29.                 *tail = n ;  
  30.             }  
  31.         }  
  32.     }  
  33.   
  34.     ListNode *sortList(ListNode *head) {  
  35.         if (!head) return NULL ;  
  36.         if (head->next == NULL) return head ;  
  37.         //划分  
  38.         ListNode *tmpNode = head ;  
  39.         head = head->next ;  
  40.         ListNode *sleft = NULL , *eleft = NULL ;  
  41.         ListNode *sright = NULL , *eright = NULL ;  
  42.         while (head)  
  43.         {  
  44.             ListNode *insNode = head ;  
  45.             head = head->next ;  
  46.   
  47.             insNode->next = NULL ;  
  48.             if (insNode->val > tmpNode->val)  
  49.                 insertBack(&sright, &eright, insNode) ;  
  50.             else  
  51.                 insertBack(&sleft, &eleft, insNode) ;  
  52.         }  
  53.   
  54.         //递归调用    
  55.         sleft = sortList(sleft) ;  
  56.         sright = sortList(sright) ;  
  57.   
  58.         //下面三句话第一次没有加上,调试了一下午才找到原因  
  59.         eleft = sleft ;  
  60.         if (eleft)  
  61.         {  
  62.             while(eleft->next)  
  63.                 eleft = eleft->next ;  
  64.         }  
  65.   
  66.         //拼接起来  
  67.         if (eleft)  
  68.         {  
  69.             head = sleft ;  
  70.             eleft->next = tmpNode ;  
  71.         }  
  72.         else  
  73.             head = tmpNode ;  
  74.         tmpNode->next = sright ; //连接起来  
  75.   
  76.         //返回结果  
  77.         return head ;  
  78.     }  
  79. };  
  80.   
  81. int main(int argc, char** argv)  
  82. {  
  83.     ifstream in("data.txt") ;  
  84.     ListNode* head = NULL ;  
  85.     ListNode* tail = NULL ;  
  86.     int val ;  
  87.   
  88.     Solution s ;  
  89.     while(in >> val)  
  90.     {  
  91.         ListNode*tmp = new ListNode(val) ;  
  92.         s.insertBack(&head, &tail, tmp) ;  
  93.     }  
  94.     head = s.sortList(head) ;  
  95.     while(head)  
  96.     {  
  97.         cout << head->val << " " ;  
  98.         head = head->next ;  
  99.     }  
  100.     cout << endl ;  
  101.     return 0 ;  
  102. }  

第二种方案:使用快排的另一种思路来解答。我们只需要两个指针pq,这两个指针均往next方向移动,移动的过程中保持p之前的key都小于选定的keypq之间的key都大于选定的key,那么当q走到末尾的时候便完成了一次划分点的寻找。如下图所示:



实现代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <string>  
  3. #include <algorithm>  
  4. #include <stack>  
  5. #include <vector>  
  6. #include <fstream>  
  7. using namespace std;  
  8.   
  9. struct ListNode {  
  10.     int val;  
  11.     ListNode *next;  
  12.     ListNode(int x) : val(x), next(NULL) {}  
  13. };  
  14.   
  15. class Solution {  
  16. public:  
  17.     ListNode* getPartation(ListNode *start, ListNode *end)  
  18.     {  
  19.         if (start == end) return start ;  
  20.   
  21.         ListNode *p1 = start ;  
  22.         ListNode *p2 = p1->next ;  
  23.         int key = start->val ;  
  24.   
  25.         while(p2 != end)  
  26.         {  
  27.             if (p2->val < key)  
  28.             {  
  29.                 p1 = p1->next ;  
  30.                 swap(p1->val, p2->val) ; //找到一个比key小的数字,与p1到p2间的数交换,  
  31.             }                                       //这之间的数都大于等于key  
  32.             p2 = p2->next ;  
  33.         }  
  34.         swap(start->val, p1->val) ; //找到划分位置  
  35.         return p1 ;  
  36.     } ;  
  37.   
  38.     void QuickSort(ListNode* start, ListNode *end)  
  39.     {  
  40.         if (start != end)  
  41.         {  
  42.             ListNode *pt = getPartation(start, end) ;  
  43.             QuickSort(start, pt) ;  
  44.             QuickSort(pt->next, end) ;  
  45.         }  
  46.     }  
  47.   
  48.     ListNode *sortList(ListNode *head) {  
  49.         QuickSort(head, NULL) ;  
  50.         return head ;  
  51.     }  
  52. };  
  53.   
  54. void insertBack(ListNode** head, ListNode** tail,  ListNode* n) //从尾部插入  
  55. {     
  56.     if (n)  
  57.     {  
  58.         if (*head == NULL)  
  59.         {  
  60.             *head = n ;  
  61.             *tail = n ;  
  62.         }  
  63.         else  
  64.         {  
  65.             (*tail)->next = n ;  
  66.             *tail = n ;  
  67.         }  
  68.     }  
  69. }  
  70.   
  71. int main(int argc, char** argv)  
  72. {  
  73.   
  74.     ifstream in("data.txt") ;  
  75.     ListNode* head = NULL ;  
  76.     ListNode* tail = NULL ;  
  77.     int val ;  
  78.   
  79.     Solution s ;  
  80.     while(in >> val)  
  81.     {  
  82.         ListNode*tmp = new ListNode(val) ;  
  83.         insertBack(&head, &tail, tmp) ;  
  84.     }  
  85.     head = s.sortList(head) ;  
  86.     while(head)  
  87.     {  
  88.         cout << head->val << " " ;  
  89.         head = head->next ;  
  90.     }  
  91.     cout << endl ;  
  92.     return 0 ;  
  93. }  
虽然使用快速排序的两种方案都因为超时不能AC,但是练习一下还是很有帮助的。

如果大家发现那里不对的地方还请批评指正,大家共同学习进步!先行谢过!

0 0
原创粉丝点击