排序的本质可以这样来表述:一组未排序的N个数字,它们一共有N!种重排,其中只有一种排列是 满足题意的(譬如从大到小排列)。换句话说,排序问题的可能性一共有N!种。任何基于比较的排序的基本操作单元都是“比较a和b”,这就相当于猜数字游戏 里面的一个问句,显然这个问句的答案只能是“是”或“否”,一个只有两种输出的问题最多只能将可能性空间切成两半,根据上面的思路,最佳切法就是切成 1/2和1/2。也就是说,我们希望在比较了a和b的大小关系之后,如果发现a<b的话剩下的排列可能性就变成N!/2,如果发现a>b也是 剩下N!/2种可能性。由于假设每种排列的概率是均等的,所以这也就意味着支持a<b的排列一共有N!/2个,支持a>b的也是N!/2个, 换言之,a<b的概率等于a>b的概率。
我们希望每次在比较a和b的时候,a<b和a>b的概率是均等的,这样我们就能保证无论如何都能将可能性缩小为原来的一半了!最优下界。
一个直接的推论是,如果每次都像上面这样的完美比较,那么N个元素的N!种可能排列只需要log_2{N!}就排查玩了,而log_2{N!}近似于NlogN。这正是快排的复杂度。
我们考虑快排的过程:随机选择一个元素做“轴元素”,将所有大于轴元素的移到左边,其余移到右边。根据这个过程,快排的第一次比较就是将一个元素和轴元素比较,这个时候显而易见的是,“大于”和“小于”的可能性各占一半。这是一次漂亮的比较。
然而,快排的第二次比较就不那么高明了:我们不妨令轴元素为pivot,第一次比较结果是a1<pivot, 那么可以证明第二次比较a2也小于pivot的可能性是2/3!这容易证明:如果a2>pivot的话,那么a1,a2,pivot这三个元素之间 的关系就完全确定了——a1<pivot<a2,剩下来的元素排列的可能性我们不妨记为P(不需要具体算出来)。而如果 a2<pivot呢?那么a1和a2的关系就仍然是不确定的,也就是说,这个分支里面含有两种情况:a1<a2<pivot,以及 a2<a1<pivot。对于其中任一种情况,剩下的元素排列的可能性都是P,于是这个分支里面剩下的排列可能性就是2P。所以当 a2<pivot的时候,还剩下2/3的可能性需要排查。
可能有人到这里还不是很明白,为什么a2<pivot的可能性是2/3。再次尝试解释一下。
当a1<pivot后,剩下的元素排列的可能性记为M,a2<pivot的概率为p1,则 M*p1 为 a2<pivot 之后剩下的元素排列的可能性,记为N;
a2>pivot 的概率为p2,则 M*p2 为 a2 > pivot 之后剩下的元素排列的可能性,根据上面的分析,应为 2N,从而可得,P2 = 2* P1,另外 p1 + p2 = 1,故 p2 = 2/3, p1 = 1/3
再进一步,如果第二步比较果真发现a2<pivot的话,第三步比较就更不妙了,模仿上面的推理,a3<pivot的概率将会是3/4!
这就是快排也不那么快的原因,因为它也没有做到每次比较都能将剩下的可能性砍掉一半。
传统的解释是:基排不是基于比较的,所以不具有后者的局限性。话是没错,但其实还可以将它和基于比较的排序做一个类比。
基排的过程也许是源于我们理顺一副牌的过程:如果你有N(N<=13)张牌,乱序,如何理顺呢?我们假象桌上 有十三个位置,然后我们将手里的牌一张一张放出去,如果是3,就放在位置3上,如果是J,就放在位置11上,放完了之后从位置1到位置13收集所有的牌 (没有牌的位置上不收集任何牌)。
我们可以这样来理解基排高效的本质原因:假设前i张牌都已经放到了它们对应的位置上,第i+1张牌放出去的时候,实 际上就相当于“一下子”就确立了它和前i张牌的大小关系,用O(1)的操作就将这张牌正确地插入到了前i张牌中的正确位置上,这个效果就相当于插入排序的 第i轮原本需要比较O(i)次的,现在只需要O(1)了。
但是,为什么基排能够达到这个效果呢?上面只是解释了过程,解释了过程不代表解释了本质。
当i张牌放到位之后,放置第i+1张牌的时候有多少种可能性?大约i+1种,因为前i张牌将13个位置分割成了 i+1个区间——第i+1张牌可以落在任意一个区间。所以放置第i+1张牌就好比是询问这样一个问题:“这张牌落在哪个区间呢?”而这个问题的答案有 i+1种可能性?所以它就将剩下来的可能性均分成了i+1份(换句话说,砍掉了i/i+1的可能性!)。再看看基于比较的排序吧:由于每次比较只有两种结 果,所以最多只能将剩下的可能性砍掉一半。
这就是为什么基排要快得多。而所有基于比较的排序都逃脱不了NlogN的宿命。
(本篇文章主要参考数学之美)
原文:http://www.cnblogs.com/sjjsxl/p/5719739.html