快速排序最好时间复杂度O(nlgn),平均时间复杂度O(nlgn),最坏的时间复杂度O(n²)(极少出现),辅助空间O(lgn),是不稳定的排序算法。
与归并排序一样,快速排序也使用分治思想,对数组A[ p...r ] 进行快速排序的三步分治过程:
分解:数组A[p...r]被划分为两个(可能为空)的子数组A[p...q-1]和A[q+1...r],使得A[ p ...q-1]中的每一个元素都小于等于A[q],而A[q]也小于等于A[q+1...r]中的每一个元素
解决:通过递归调用快速排序,对子数组A[p...q-1]和A[q+1...r]进行排序。
合并:因为子数组是原址有序的,所以不需要合并操作,数组A[p...r]已经有序。
伪代码实现:
QuickSort (A , p , r )
if p< r
q = Partition(A , p , r )
QuickSort(A , p , q-1 )
QuickSort(A , q+1 , r)
为排序一个数组A的全部元素,初始调用的是QuickSort(A , 1 , A.length )。
其中,算法的关键部分Partition过程,它实现了对子数组A[p...r]的原址重排。
Partition(A , p , r )
x = A[r]
i = p - 1
for j=p to r-1
if A[j] ≤ x
i = i+1
exchange A[i] with A[j]
exchange A[i+1] with A[r]
return i+1
快速排序算法Java版:
import java.util.Arrays;
public class QuicklySort {
private static int N = 10;
public static void main(String[] args) {
int[] arr = new int[N];
for (int i = 0; i < N; i++) {
arr[i] = ((Double) (Math.random() * 100)).intValue();
}
System.out.println("排序前的数组 : " + Arrays.toString(arr));
quickSort(arr, 0, N - 1);
System.out.println("排序后的数组 : " + Arrays.toString(arr));
}
/**
* @param arr 待排序数组
* @param left 排序起始下标
* @param right 排序终止下标
*/
public static void quickSort(int[] arr, int left, int right) {
if (left < right) { // 递归控制
int i = left, j = right, x = arr[i];
while (i < j) {
while (i < j && x <= arr[j])
j--;
if (i < j) {
arr[i] = arr[j];
i++;
}
while (i < j && arr[i] < x)
i++;
if (i < j) {
arr[j] = arr[i];
j--;
}
}
arr[i] = x;
quickSort(arr, 0, i - 1); // 递归排序左边
quickSort(arr, i + 1, right); // 递归排序右边
}
}
}
快速排序的性能
快速排序的性能依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快速排序算法的性能与归并排序是一样的。如果划分是不平衡的,那么快速排序的性能就接近于插入排序。
最坏情况:当划分产生的两个子数组分别包含了n-1和0个元素,那么快速排序的最坏情况发生了,实际上也就变成了插入排序,此时的时间复杂度是O(n²)。需要注意的是,最坏情况下,快速排序的运行时间并不比插入排序好,当输入的数组完全有序时,插入排序的时间复杂度是O(n),而快速排序的时间复杂度仍然是O(n²)。
最好情况:在可能的最平衡划分中,划分得到的两个子数组规模都不大于n/2,也就是如果规模为10,那么两个子数组规模一个是4一个是5(如果A的规模是11那么两个子数组的规模都是5),此时的时间复杂度是O(nlgn)。
平均情况:快速排序的平均运行时间接近于其最好的情况,而非最坏的情况,这也是平均复杂度和最好复杂度都是O(nlgn)的原因