链表归并排序的递归与非递归实现
来源:互联网 发布:windows任务栏不见了 编辑:程序博客网 时间:2024/04/26 09:02
归并排序算法交换链表节点,时间复杂度为O(NlogN),不考虑递归栈空间的话空间复杂度是O(1) ,考虑的话,空间复杂度为O(logN)。算法思想是首先用快慢指针的方法找到链表中间节点,然后递归地对两个子链表进行排序,把两个排好序的子链表合并成一条有序的链表。归并排序算是链表排序中的最好选择,保证了最好和最坏时间复杂度都是NlogN,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(N)降到了O(1)。
递归实现为:
class Solution {public: ListNode* sortList(ListNode* head) { if (head==NULL||head->next==NULL) { return head; } ListNode* fast = head, *slow = head; while (fast->next&&fast->next->next) { //通过快慢指针找到中间节点,while终止时,slow指向中间节点 fast = fast->next->next; slow = slow->next; } ListNode* leftHead = head, *rightHead = slow->next; //leftHead为左边的子链表,rightHead为右边的子链表 slow->next = NULL; ListNode* left = sortList(leftHead); ListNode* right = sortList(rightHead); ListNode newHead(0), *link = &newHead; while (left&&right) { //进行归并排序 if (left->val<=right->val) { link->next = left; left=left->next; } else { link->next = right; right = right->next; } link = link->next; } if (left) { link->next = left; } if (right) { link->next = right; } return newHead.next; }};非递归实现 (下面的内容来自博文http://www.cnblogs.com/bin3/articles/1858691.html)
面创新工场时被问到链表排序题。当时思路混乱,没有想出时间空间均较优的方法。后来再想,至少能用归并排序嘛,即使实现得不优美。这充分体现了我思维方法的一个不足,面对新问题有时会陷入东敲西打浅尝辄止的胡思乱想,而忽视了从基本方法出发稍加变通便能解决新问题的思路。
再一翻侯捷的《STL源码剖析》中介绍的SGI STL中list的sort函数的实现,修改其他无关细节之后的代码如下:
template<class T>void list_sort(list<T>& lst){ if (lst.size() > 1) { list<T> carry; list<T> counter[64]; int fill = 0; while (!lst.empty()) { carry.splice(carry.begin(), lst, lst.begin()); int i = 0; while(i < fill && !counter[i].empty()) { counter[i].merge(carry); carry.swap(counter[i++]); } carry.swap(counter[i]); if (i == fill) ++fill; } for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]); lst.swap(counter[fill-1]); }}
侯捷的注释是本函数采用的是快速排序的方法。我硬着头皮看了多遍硬是没看懂。网上的几篇帖子也没有说明白。直到把中间结果输出才明白,侯捷的注释是错误的,这其实是归并排序的非递归实现。
要理解这个实现,首先明确几个函数的作用:
void list::splice( iterator pos, list& lst, iterator del );
splice把lst中del所指元素删除并插入到当前list的pos位置上。
void list::merge( list &lst );
merge把lst的元素合并到当前list,参数lst的元素会被清空的。
再明确几个变量的作用:
counter[i]如不空,则存储2^i个已排好序的元素。
carry只是起中转作用。
fill标记非空counter数组元素的下标i的上界,初始时为0。
该实现可这样理解:
第9-20行是主循环,每次删除lst的首元素并将其放入counter数组列表的合适位置,直至lst为空。
第11行将lst首元素移动到carry中。
第12-18行从counter[0]开始,如当前处理的元素counter[i]非空,则归并carry与counter[i],将结果放到carry中并把counter[i]置空,以此类推处理后一个counter元素,直到当前处理的counter元素为空。这样的处理能一直保持counter[i]的特性,即如不空则存储2^i个已排好序的元素。
第19行在适当时候更新fill值。
第21-23行将counter数组的所有元素归并,并将最终的排序结果交回给lst。
可以看一个运行实例。假设lst包含元素“7 9 0 6 10 4 0 7 5 1 0”。以下输出为从左往右每把一个lst的元素放入counter后,counter数组的存储内容。
+ 7
[0] 7
+ 9
[0]
[1] 7 9
+ 0
[0] 0
[1] 7 9
+ 6
[0]
[1]
[2] 0 6 7 9
+ 10
[0] 10
[1]
[2] 0 6 7 9
+ 4
[0]
[1] 4 10
[2] 0 6 7 9
+ 0
[0] 0
[1] 4 10
[2] 0 6 7 9
+ 7
[0]
[1]
[2]
[3] 0 0 4 6 7 7 9 10
+ 5
[0] 5
[1]
[2]
[3] 0 0 4 6 7 7 9 10
+ 1
[0]
[1] 1 5
[2]
[3] 0 0 4 6 7 7 9 10
+ 0
[0] 0
[1] 1 5
[2]
[3] 0 0 4 6 7 7 9 10
最后归并counter中的所有元素得到最终的排序结果“0 0 0 1 4 5 6 7 7 9 10”。
要方便记住上面的算法,可以用一个情形来进行记忆:counter[0]有一个元素1,counter[1]有两个元素2和3,fill为2,现在从lst中新加入一个元素0,则carry首先为0,然后i为0<fill并且counter[0]不为空,则counter[0]先merge carry然后再与carry交换,这个时候carry为0和1。然后i为1<fill并且counter[1]不为空则counter[1]先merge carry然后再与carry交换,这个时候carry为0,1,2,3,接着i为2==fill,则退出循环。这个时候,carry与counter[2]交换,则counter[2]为0,1,2,3。i与fill相等,则fill为3。最后不再有其他元素,则把所有元素合并到counter[2],再与lst进行交换。
这其实是一个链表上的归并排序的非递归实现。好处如下:
1.使用归并排序保证了最坏的时间复杂度为O(nlog(n))。2.利用链表结构使归并的过程既只需使用常数的额外空间,时间上又很高效。3.消除了递归实现的开销。
该实现将链表数据结构和归并排序算法的优势结合得天衣无缝,令人叹服。赞!
- 链表归并排序的递归与非递归实现
- 归并排序 递归与非递归实现
- 归并排序递归与非递归实现
- 归并排序的递归实现与非递归实现
- 归并排序的递归实现与非递归实现
- 归并排序递归与非递归的实现
- 归并排序(递归与非递归)的实现
- 归并排序的递归与非递归实现Java
- 递归到非递归转换——归并排序与快排的非递归实现
- 归并排序的非递归实现
- 归并排序的非递归实现
- 归并排序的非递归实现
- 归并排序的非递归实现
- 归并排序的非递归实现
- 非递归实现归并排序
- 非递归实现归并排序
- 归并排序非递归实现
- 归并排序(非递归实现)
- source insight配置文件
- HDU 5463
- Weka各类分类器的使用(Java)
- 23设计模式之命令模式(Command)
- Cordova-iOS自定义插件以及和老版本的差异
- 链表归并排序的递归与非递归实现
- NSURLProtocol的使用
- 大数据学习篇:hadoop深入浅出系列之HDFS(一)——HDFS简介和优缺点
- 潘鹏整理WPF(4)工具提示ToolTip&&Popup
- 不含数据库的登录实现
- PCB-从零开始
- 病毒查杀
- Balanced Binary Tree
- web前端开发的点点滴滴---3.CSS介绍