算法实现请移步这里
前面已经陆续有:选择排序 ,冒泡排序,插入排序,希尔排序,归并排序
可以看到,是按照排序算法的性能来罗列的.
快速排序和归并排序一样,也是"分治思想"的经典应用
快速排序能很好的适用于各种各种的随机输入,并且大多数情况下,都要优于以上其他算法,并且只需要有限的辅助数组(归并排序需要额外的等量的内存空间),它的内存换也是相当的简单.
上面提到它跟归并排序一样,也是使用"分治思想"来进行算法设计,所以,快速排序也是递归实现的排序算法
算法描述:
快速排序将数组递归的分成两半,然后分别对他们排序.
选定第一个元素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(由于项目忙,可能会晚于这个时间点,但是一定会尽量完成!)
下一篇:堆排序