快速排序的基本原理

前言:
今天凌晨在临睡前随意翻看了 @啊哈磊 的坐在马桶上看算法:快速排序,对快速排序算法有了新的认识。在此之前,我只对冒泡排序算法有一些了解。
在此感谢 @啊哈磊 的耐心讲解。
已知一个一维数组arr,包含17个元素:

int[] arr = new int[] { 13, 25, 6, 84, 71, 63, 96, 49, 7, 52, 30, 28, 1, 74, 93, 69, 40 };

我们从中选择任意一个数作为基准,例如25。
另外,声明两个指针变量a和b,将他们置于数组的两端,即13和40。
左侧指针a负责找比基准值大的数,它从左向右移动;
右侧指针b负责找比基准值小的数,它从右向左移动。
当它们其中一个找到合适的数时,则在此停留并等待另一个指针找到合适的数。如果另一个指针也找到了合适的数,那么就令它们交换得到的数(指针b先开始移动)。
第一次的停留结果:
84,1
OK,将这两个数交换位置。这就变成了:
(1)13, 25, 6, 1, 71, 63, 96, 49, 7, 52, 30, 28, 84, 74, 93, 69, 40
a b
此时,指针a停留位置的数值是1,指针b停留位置的数值是84。
第二次停留结果:
71,7
再将这两个数交换位置:
(2)13, 25, 6, 1, 7, 63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
a b
此时,指针a停留位置的数值是7,指针b停留位置的数值是71。
这时我们会发现,7与71之间的数都比25大。这意味着,如果指针b继续向左搜寻比25小的数值,必将与指针a相遇。即:指针a与b都处在7的位置。
OK,将指针a和b共同停留的7与基准数25交换:
(3)13, 7, 6, 1, 25, 63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
ab
这时我们会发现:基准数25左侧数列中的每个数都比25小,而右侧数列中的每个数都比25大。这样,我们就能将这个数组以基准值25为界限,划分为两个数列:
13, 7, 6, 1
63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
而在这两个数列中,第一个数列的左侧指针停留的值和第二个数列的右侧指针停留的值分别与与数组的左右侧指针一致。
到这里,可能有些人会有疑问:如果在第(2)步之后,率先移动的是指针a而不是指针b,那么是否可以呢?
我可以很明确地对你说——完全可以!
至于为什么,我们接下来继续解析:
假如我们率先移动的是指针a而不是指针b,那么在指针a率先移动并停留于大于基准数的数值63后,指针b在向左寻找小于基准数25的数值时,将与指针a相遇。即:
(4)13, 25, 6, 1, 7, 63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
ab
我们会观察到:63左侧的所有数都不大于25,而63及右侧的所有数都不小于25。
同样,我们可以将数组(4)分为两个数列:
13, 25, 6, 1, 7
63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
而在数列(3)中,我们会发现(4)的结论同样使用于(3):
13, 7, 6, 1, 25, 63, 96, 49, 71, 52, 30, 28, 84, 74, 93, 69, 40
ab
结论与(4)类似:25左侧的所有数都不大于25,而25及右侧的所有数都不小于25。
只不过在(3)中,我们得出的结论是:25左侧的所有数都小于25,而右侧的所有数都大于25。
根据上面的推导过程,我们很容易再将这两种类型的子数列依照新的基准数进行排序。
其实快速排序与冒泡排序的最大不同之处在于,后者的排序是渐进式(每次比较的两个数都相邻),而前者的排序是跳跃式。当预排序的数组足够长且比较次数一定时,涉及更多新的数值的跳跃式无疑具有渐进式不可比拟的优点。当然,前者与后者在最坏情况下的比较次数是相等的。
不过,不要忘了:快速排序的基准必须是当前排序数列的最大值与最小值之间的一个数。否则,很容易发生StackOverflowException。

猜你喜欢

转载自blog.51cto.com/13719203/2158569