快速排序基本思想
基于分治,是对冒泡排序的一种改进。
首先选取一个基准点(pivot),为计算简单,暂且选取数组的第一位为基准点,并将其存储到临时变量中。
事实上,基准点的选取一定程度上影响着快速排序的速率,目前有许多中选取基准点的方案,比如产生随机索引,取前中后的中位数等等。
以该基准点为依据,将数组分为两部分。定义两个指针,其中左指针从左向右扫描,找到比基准大的数,移到右边,右指针从右向左扫描,找到比基准小的数,移到左边。循环往复之后,最终high指针指向的位置即为pivot应该存在的位置,可得到pivot的索引。
代码实现
//用户接口
public static void quickSort(int[] arr){
quickSort(arr,0,arr.length-1);
}
//快速排序,分而治之
private static void quickSort(int[] arr, int start, int end) {
if(end>start){
//通过partition方法获取pivot的位置
int pivotIndex = partition(arr,start,end);
quickSort(arr,start, pivotIndex);
quickSort(arr,pivotIndex+1,end);
}
}
//排序,并返回pivot的正确位置
private static int partition(int[] arr, int low, int high) {
// 取第一位为基准
int pivot = arr[low];
while (low < high) {
// 从后往前找到比pivot小(相等的情况忽略)的元素位置
while (low < high && arr[high] >= pivot) {
high--;
}
// 将这个位置移到左边low阵营
arr[low] = arr[high];
// 从前向后找到比pivot大(相等的情况忽略)的元素
while (low < high && arr[low] <= pivot) {
low++;
}
// 将这个位置移到右边high阵营
arr[high] = arr[low];
}
// low==high相等,跳出循环 此时将pivot插入属于他的位置
arr[low] = pivot;
// 返回pivot的位置
return low;
}
时间复杂度
最坏的情况下:基准元素每次选的都是最小的,那么将会把数组划分为一个空数组和一个n-1长的大数组,最终的复杂度:(n-1)+(n-2)+……+2+1 = O(n^2),是让人难以接受的,因此基准点的选取很重要。
最好的情况下:基准元素每次都选的很好,都正好将数组划分为规模大致相等的两个部分。T(n) = T(n/2)+T(n/2)
+n。与归并排序类似,时间复杂度为O(nlogn)。
可以发现,在最坏的情况下,归并排序的效率要比快速排序优秀,但平均情况下两者效率相似,但快速排序不需要额外的数组空间用于归并,在空间效率上优于归并排序。
快速排序的问题
可以发现,每一次的分,基准元素都将直接插入到自己的位置,这一点不同于插入排序。这也是快速排序快的地方。
使用递归,将会额外占用系统堆栈的空间。对于小规模的数据,可能不如插入排序。
当递归的数据规模充分小的时候,应该停止递归,直接调用简单排序。