链表排序---由底至顶的归并排序更优雅

在leetcode中有这么一道题click meSort 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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值