快速排序
原理
快速排序(Quick Sort)算法,简称快排,利用的也是分治的思想,快排的思路是:如果要对 m->n 之间的数列进行排序,我们选择 m->n 之间的任意一个元素数据作为分区点(Pivot),然后我们遍历 m->n 之间的所有元素,将小于 pivot 的元素放到左边,大于 pivot 的元素放到右边,pivot 放到中间,这样整个数列就被分成三部分了,m->k-1 之间的元素是小于 pivot 的,中间是 pivot,k+1->n 之间的元素是大于 pivot 的。然后再根据分治递归的思想处理两边区间的的元素数列,直到区间缩小为 1,就说明整个数列都已有序了。
算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序,直到区间缩小为1。
代码实现
public class QuickSort {
public static void main(String[] args) {
int[] arr = {
5, 2, 6, 9, 0, 3, 4};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr, int begin, int end) {
if (arr.length <= 1 || begin >= end) {
return;
}
//进行分区得到分区下标
int pivotIndex = partition(arr, begin, end);
//分区左侧进行快排
quickSort(arr, begin, pivotIndex - 1);
//分区右侧进行快排
quickSort(arr,pivotIndex+1,end);
}
public static int partition(int[] arr, int begin, int end) {
//默认使用 最后一个元素作为基准点
int pivot = arr[end];
//定义分区后的pivot元素下标
int pivotIndex = begin;
for (int i = begin; i < end; i++) {
判断如果该区间内如果有元素小于 pivot 则将该元素从区间头开始一直向后填充 有点类似选择排序
if (arr[i] < pivot) {
if (i > pivotIndex) {
swap(arr,i,pivotIndex);
}
pivotIndex++;
}
}
swap(arr,pivotIndex,end);
//得到基准点
return pivotIndex;
}
//交换元素
public static void swap(int[] arr, int i, int j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
1:快速排序的时间复杂度是多少?
快排的时间复杂度最好以及平均情况下的复杂度都是 O(nlogn),只有在极端情况下会变成 O(n^2)。
2:快速排序的空间复杂度是多少?
通过快排的代码实现我们发现,快排不需要额外的存储空间,所有的操作都能在既定的空间内完成,因此快排的空间复杂度为 O(1),也就是说快排是一种 in-place的排序算法。
3:快速排序是稳定的排序算法吗?
因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6,8,7,6,3,5,9,4,在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变。所以,快速排序并不是一个稳定的排序算法。
4:快排和归并的异同
首先快排和归并都用到了分治递归的思想,在快排中对应的叫分区操作,递推公式和递归代码也非常相似,但是归并排序的处理过程是由下到上的由局部到整体,先处理子问题,然后再合并。而快排正好相反,它的处理过程是由上到下由整体到局部,先分区,然后再处理子问题。归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是一种 out-place 排序算法。主要原因是合并函数无法在原地(数组内)执行。快速排序通过设计巧妙的原地(数组内)分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。