快速排序的几种分割策略
快速排序的基本思想是在待排序数据中选取一个pivot数据,将剩余数据按照与pivot的比较结果分割成两份,小的放在pivot左边,大的放在pivot右边(假设是升序排序),这样pivot已经在正确的位置上了。然后对pivot的左右待排数据继续递归调用上述过程。到待排数据长度<=1的时候递归返回,当所有递归都返回,就完成了整个数据的排序。
如何分割是快速排序的关键所在。这里提三种不同的分割方式。
所谓分割就是要对所有数据的位置进行重新安排,使得所有比pivot小的数据位于pivot左侧,所有比pivot大的数据位于pivot右侧。
第一种策略采用空位vacancy的概念。用low和high标记未检测区域的边界,起初将E[low]作为pivot保存在本地变量中,这样E[low]成为一个空位,用vacancy标记。为了找到一个元素放在这个空位里(应该是小于pivot的),而且为了尽可能远地移动它(通过一次比较而将元素移动尽可能远的距离可能争取到更高的排序效率),我们从high开始向左查找第一个大于pivot的数,找到后将它移动到vacancy里,这样该处留下了一个空位标记为highVac,同时更新high=highVac,因为右侧的数据都已经检查过了,都比pivot大,在接下来的过程中不需要在移动。接下来从low处开始向右查找第一个比pivot大的元素(同样,为了将它放在刚空出来的highVac中,而且希望移动尽可能远的距离),找到后将其移动到highVac中,将新空出来的空位用lowVac标记,并且更新low=lowVac。至此,我们完成了一次迭代,更新了low和high,使得未被检测的区域变窄了,而且low左侧的数据都比pivot小,high右侧的数据都比pivot大,low处为一个空位,这和迭代开始的条件是一致的,因此迭代可以进行下去。直到某一次查找大于或小于pivot元素的过程中一直到对面的空位都没有找到,则说明所有元素的位置都已经被安排好,这时候再把保存在本地变量中的pivot放到空位里,就完成了分割。
第二种策略与第一种类似,也是从两侧向中间推进,但是没有使用空位的概念,而是采用交换的方法。将pivot放在E[low]处,从E[low+1]和E[high]分别开始向中间查找第一个大于pivot的和第一个小于pivot的数据,找到后将两者进行交换,然后继续向中间推进,直到两个方向的查找相遇。然后交换E[low]和最后一个小于pivot的数据,就完成了分割。
第三种策略是单向的策略,基本思想是时刻更新标记小于pivot的最后一个数据位置m初始为low-1。在按照下标i遍历的过程中,如果E[i]>pivot那么一切正常,m不需要更新,E[i]在m之右;如果E[i]<pivot,那么说明m需要增一,然后需要将E[i]与E[m]交换,这样就维持了m标记最后一个比pivot小的数据这样一个不变式。最后遍历完成之后将E[m]与位于队头的pivot进行交换,即完成了分割。
