算法实现请移步这里
前面已经陆续有:选择排序 ,冒泡排序,插入排序,希尔排序,归并排序
可以看到,是按照排序算法的性能来罗列的.
快速排序和归并排序一样,也是"分治思想"的经典应用
快速排序能很好的适用于各种各种的随机输入,并且大多数情况下,都要优于以上其他算法,并且只需要有限的辅助数组(归并排序需要额外的等量的内存空间),它的内存换也是相当的简单.
上面提到它跟归并排序一样,也是使用"分治思想"来进行算法设计,所以,快速排序也是递归实现的排序算法
算法描述:
快速排序将数组递归的分成两半,然后分别对他们排序.
选定第一个元素K(a[0])作为切分元素(用来将数组切分为两半),将数组进行切分成三部分
- 左边(a[low]...a[j-1]),
- 切分元素,a[j]
- 右边(a[j+1]....a[height])
然后对左边进行相同的切分,再对右边进行相同的切分,整个过程是递归的.
切分算法的结果保证
a[j]一定在正确的位置j
a[low]...a[j-1]都不大于a[j]
a[j+1]....a[height]都不小于a[j]
切分算法的过程是这样的:
为了获取正确的a[j],我们每次都选择a[low](待切分数组的第一个元素)作为位置j的切分元素,然后分别从数组的两端开始遍历:从左到右依次遍历数组元素,直到a[i] >=a[low]时停止,从右到左依次遍历,直到a[j]<=a[low]时停止,然后交换a[i]和a[j]元素,然后继续前面的遍历,直到数组结束.最终结束时的j就是切分元素a[low]的正确位置.遍历的过程为:
切分的算法实现为:
private static int partition(int[] array, int low, int height) { //partition the array to array[low]...a[j-1], a[j], a[j+1]...a[height] int i = low; int j = height + 1; int value = array[low];// the partition value //exchange the left and right along with scan int temp; while (true) { while (array[++i] <= value) if (i >= height) break; while (array[--j] >= value) if (j <= low) break; if (i >= j) break; //exchange the array[i] and array[j] temp = array[i]; array[i] = array[j]; array[j] = temp; } //exchange the array[low] and array[j] temp = array[low]; array[low] = array[j]; array[j] = temp; return j; }
具体的切分排序过程为:
当我们将数组经过一次切分后,就可以使用递归的方式,对剩下的数组进行相同的切分排序,最终直到整个数组排序
其实每次切分后都能保证a[j]左边的元素都不大于a[j], a[j]右边的元素都不小a[j],那么只需要对剩下的数组继续使用切分算法就好了,但是被切分的两半数组内部是没有顺序的,最终数组在递归的切分下,最终还是变成有序数组了?怎么做到的呢?本质与归并排序类似,我们递归排序的中,我们总是递归的将数组进行对半分,直到子数组的长度变成1时,才使用merge方法进行元素的比较,而且这种比较是基于子数组本身有序来保证的,只有当子数组长度为1时,才保证了子数组自身的有序特征,这个时候才能开始merge(归并),只有基于这个基础才能保证归并排序的正确性,所以归并排序不管是自顶向下,还是自底向上的归并,最终调用merge之前,子数组必须有序!同样的,每次切分后,被切分成的子数组本身是没有顺序的,他们只是不小于或者不大于a[j],但是当切分的数组长度也为1时,只剩下两个元素的比较,这个时候就变成有序的,从这个点开始往回溯,所有的有序数组组合成更大的有序数组!
整个排序过程为:
红色元素是切分元素!
整个算法的实现为:
public class QuickSort { public static void main(String[] args) { int[] nums = RandomSequence.retrieveSequence(); long previousTime = System.currentTimeMillis(); sort(nums, 0, nums.length - 1); System.out.println("Quick sort cost " + (System.currentTimeMillis() - previousTime)); } public static void sort(int[] array, int low, int height) { if (low >= height) return; int j = partition(array, low, height); sort(array, low, j - 1); sort(array, j + 1, height); } private static int partition(int[] array, int low, int height) { //partition the array to array[low]...a[j-1], a[j], a[j+1]...a[height] int i = low; int j = height + 1; int value = array[low];// the partition value //exchange the left and right along with scan int temp; while (true) { while (array[++i] <= value) if (i >= height) break; while (array[--j] >= value) if (j <= low) break; if (i >= j) break; //exchange the array[i] and array[j] temp = array[i]; array[i] = array[j]; array[j] = temp; } //exchange the array[low] and array[j] temp = array[low]; array[low] = array[j]; array[j] = temp; return j; } }
可以看出,sort(int[] array, int low, int height) 的递归调用仅仅是为了安排合理的
partition(int[] array, int low, int height)方法调用
递归调用在小规模数组上是很不利的,会过多的在小规模数组上进行递归调用,所以我们像在归并排序中的优化那样,对小规模的数组不再使用递归,而是使用插入排序,例如:
public static void sort(int[] array, int low, int height) { // if (low >= height) return; if (low + 10 >= height) {// fast then original quick sort about 200ms at size of 1<<24 InsertSort.sort(array, low, height); return; } int j = partition(array, low, height); sort(array, low, j - 1); sort(array, j + 1, height); }
在千万级别的数组规模下,可以提升200-300ms的时间
有时间再介绍三向切分法,应对大量重复元素的情况下,可以极大的提升排序性能,后面会做排序性能的检测
算法复杂度分析
完成时间-2018-04-28(由于项目忙,可能会晚于这个时间点,但是一定会尽量完成!)
下一篇:堆排序