在leetcode中有这么一道题click me,Sort a linked list in O(n log n) time using constant space complexity.
O(NlgN)的时间复杂度自然非归并排序莫属。
如果你在网上搜索一下这道题,你会发现清一色的全是由顶向下的递归归并排序,时间复杂度保证了O(NlgN),但是空间复杂度由于有系统栈的隐式存在,显然不是O(1)空间消耗。
这里先说下由顶至底和由底至顶的归并排序有何不同。
给定一组数据6,7,4,3,1,9,8
由顶至底的归并排序过程:
--排序6,7,4,3
----排序6,7
------排序6
------排序7
----归并得到有序子数组6,7。 此时数组为6,7,4,3,1,9,8
----排序4,3
------排序4
------排序3
----归并得到有序子数组3,4。 此时数组为6,7,3,4,1,9,8
--归并得到有序子数组3,4,6,7。 此时数组为3,4,6,7,1,9,8
--排序1,9,8
----排序1,9
------排序1
------排序9
----归并得到有序子数组1,9。 此时数组为3,4,6,7,1,9,8
----排序8
----归并得到有序子数组1,8,9。此时数组为3,4,6,7,1,8,9
--归并有序子数组得到1,3,4,6,7,8,9
由顶至底的归并排序是待排序的子数组越来越小的过程,而由底至顶的归并排序是待归并的子数组越来越大的过程。
第一步,待归并的子数组长度是1,数组切分如下
[6,7],[4,3],[1,9],[8]
归并后得到[6,7],[3,4],[1,9],[8]
第二步,待归并的子数组长度是2,数组切分如下
[6,7,4,3],[1,9,8]
归并后得到[3,4,6,7],[1,8,9]
第三步,待归并的子数组长度是4(这次可直接将整个数组排序)。
可以想象一下,由底至顶的归并就是先取出前面的2个元素,排序,放在另一个地方,然后从剩余的数组中再取出前面的2个元素排序,加在上一个有序对的后面,如此这样直到整个数组是若干个(2个数构成的)有序对组成。
然后从数组中取出前面的4个元素(2组有序对)排序,放在另一个地方,然后再取出4个元素排序,加在上一个有序对的后面,如此这样直到整个数组是若干个(4个数构成的)有序对组成。
然后从数组中取出前面的8个元素(2个有序对)排序。。。。
。。。
这样一定能够把整个数组排序完成。
其实这样的归并更适合链表排序,因为不需要找到处于中间位置的元素(由顶至底的归并需要中间位置),并且不需要额外空间,链表只需修改结点指针即可实现原地排序。
原理就是这样,非常清晰简单,但是代码写起来还是有一定的技巧的,来研究下vs2013中list的sort方法
void sort()
{ // order sequence, using operator<
sort(less<>());
}
template<class _Pr2>
void sort(_Pr2 _Pred)
{ // order sequence, using _Pred
if (2 <= this->_Mysize)
{ // worth sorting, do it
const size_t _MAXBINS = 25;
_Myt _Templist(this->_Getal()), _Binlist[_MAXBINS + 1];
size_t _Maxbin = 0;
while (!empty())
{ // sort another element, using bins
_Templist._Splice_same(_Templist.begin(), *this, begin(),
++begin(), 1);
size_t _Bin;
for (_Bin = 0; _Bin < _Maxbin && !_Binlist[_Bin].empty();
++_Bin)
{ // merge into ever larger bins
_Binlist[_Bin].merge(_Templist, _Pred);
_Binlist[_Bin].swap(_Templist);
}
if (_Bin == _MAXBINS)
_Binlist[_Bin - 1].merge(_Templist, _Pred);
else
{ // spill to new bin, while they last
_Binlist[_Bin].swap(_Templist);
if (_Bin == _Maxbin)
++_Maxbin;
}
}
for (size_t _Bin = 1; _Bin < _Maxbin; ++_Bin)
_Binlist[_Bin].merge(_Binlist[_Bin - 1],
_Pred); // merge up
_Analysis_assume_(0 < _Maxbin);
splice(begin(), _Binlist[_Maxbin - 1]); // result in last bin
}
}
是的,STL的源码看起来就是这么的操蛋,十分没有格式。
这个算法借助一个额外数组,每个元素都指向一个链表,对于下标为X的位置,它指向的链表长度至多为2的X次方。算法思路是这样的,
每次从数组头部截取一个元素,试图放在下标为0的位置中,如果该位置没有元素则直接放入,如果该位置有元素,则与之合并,并放入下标为1的位置中,然后再检测下标为1的位置,如果还有元素则与之合并并放到下标为2的位置,,,如此,直到找到一个空的位置放入,或者到达最后一个位置则只能与最后一个位置合并了。这个算法描述起来不是很清晰,自己找几个元素推导一下就会很清晰了。
我的代码:https://github.com/coderchen/leetcode/blob/master/SortList.cpp