单链表排序(快速排序、归并排序)
2014-03-26 22:53
197 查看
本题目来源于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。
下面再说一下自己AC超时的代码吧,
这里我尝试了两种实现方案:
第一种是:
在找划分点的过程中,维护连个链表Left 和Right 所有不大于key的元素都链到Left上,大于key的链到Right上,最后再将Left, key , Right三部分连接起来。
代码如下:
第二种方案:使用快排的另一种思路来解答。我们只需要两个指针p和q,这两个指针均往next方向移动,移动的过程中保持p之前的key都小于选定的key,p和q之间的key都大于选定的key,那么当q走到末尾的时候便完成了一次划分点的寻找。如下图所示:
实现代码如下:
如果大家发现那里不对的地方还请批评指正,大家共同学习进步!先行谢过!
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。
#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,但是练习一下还是很有帮助的。
如果大家发现那里不对的地方还请批评指正,大家共同学习进步!先行谢过!
相关文章推荐
- C/C++ 数组,链表排序(平均时间复杂度 O(nlogn))归并、快速、堆、希尔之归并排序
- 单向链表排序:快速排序和归并排序
- 单链表的排序 快速排序 归并排序 quicksort mergesort
- 6种排序算法及其比较 简单选择排序,堆排序,简单插入排序,希尔排序,冒泡排序,快速排序,归并排序
- 快速排序 and 归并排序(python)
- 数据结构之进阶排序(希尔排序、快速排序、归并排序)
- 排序算法(归并排序, 快速排序, 堆排序)
- 快速排序、堆排序、归并排序
- 内排序(插入排序、冒泡排序、选择排序、shell排序、快速排序、归并排序、堆排序)
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法, 冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
- (二)排序简介:快速排序、选择排序、归并排序、堆排序
- 七大内部排序算法总结(插入排序、希尔排序、冒泡排序、简单选择排序、快速排序、归并排序、堆排序)
- 对比快速排序,理解归并排序
- 对链表进行排序(归并排序)
- lintcode刷题系列:链表排序----归并排序 递归
- 选择排序,插入排序,冒泡排序,希尔排序,快速排序,归并排序
- {希尔排序、快速排序、动态数组、单链表、字符串转整型}大综合
- 快速排序,归并排序
- 快速排序、归并排序、大顶堆排序、希尔排序代码实现
- 归并排序快速排序java代码