快速排序是冒泡排序的一种改进版本。
快速排序虽然最坏情况的时间复杂度是O(n²)(给顺序数组排列,将退化为冒泡排序),但是其性能在顺序性越差的数据中表现越好,甚至可以比归并排序要好。这是因为虽然快速排序跟归并排序的平均时间复杂度都是O(nlogn),但是快速排序的O(nlogn) 记号中隐含的常数因子很小。
代码如下:
public class Main {
public static void main(String[] args) {
int[] arr = {3, 3, 5, 6, 2, 1};
System.out.print("排序前:");
arrPrint(arr);
QuickSort(arr);
System.out.print("排序后:");
arrPrint(arr);
}
// 快速排序
// 快速排序和归并排序一样采用了分治法的设计思想。
// 把大问题分解成小问题,把大数组分解成小数组。
//
// 调用快速排序的递归函数,左索引记为left,初始化为0,
// 右索引记为right,初始化为arr.length - 1。
private static void QuickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
// 快速排序的递归函数
// 递归终止条件为左索引>=右索引。不满足终止条件时:
// 调用基准值分割函数partition,得到分割后的索引mid,
// mid-1即为分割数组后的左子数组的终点,mid+1即为分割数组后右子数组的起点
// 递归调用快速排序quickSort,对左子数组进行快速排序,
// 递归调用快速排序quickSort,对右子数组进行快速排序。
//
// 想看中间输出的可以在partition函数后面使用arrPrint(arr)来打印
private static void quickSort(int[] arr, int left, int right) {
if (left < right) {
int mid = partition(arr, left, right);
quickSort(arr, left, mid - 1);
quickSort(arr, mid + 1, right);
}
}
// 基准值分割函数partition
// 选取基准值pivot后,循环使用双指针寻找左边比pivot大的数arr[l],
// 右边比pivot小的数arr[r],并交换arr[l] arr[r]的位置。
// 使得在r和l相遇位置的左半边的数不大于pivot,而在右半边的数则不小于pivot,
// 最后把pivot交换到r和l的相遇位置,即可补全空间意义上真正的基准值分割。
// 此时pivot的位置(r和l的相遇位置)将数组分割为了两边。左半边总是不大于右半边的数字。
// 之后再利用分治法递归地调用partition,继续基准值分割左右子数组即可完成整个快排。
//
// 选取基准值pivot(默认arr是随机排序,所以直接取arr[left]),
// 将左指针初始化为left + 1,右指针初始化为right,
// 满足l小于r时(双指针没有超过遍历边界时)执行第一层while循环:
// 第2层第1个while: 如果左指针l没有超过边界,且遍历元素arr[l]不大于pivot,
// 则左指针l循环右移,直到找到从左往右第一个比pivot大的遍历数arr[l]。
// 第2层第1个while:同理,如果右指针r没有超边界,且遍历元素arr[r]不小于pivot,
// 则右指针r循环左移,直到找到从右往左第一个比pivot大的遍历数arr[r]。
// 如果此时l依然满足小于r(双指针没有过界),则将arr[l] arr[r]交换位置,
// l和r相遇后,所有while结束,此时将pivot交换到r与l的相遇位置。
// pivot记录了arr[left]的值,所以先用arr[r]把arr[left]覆盖掉,
// 再把pivot放到arr[r]上,完成最后交换,补全空间意义上真正的基准值分割,
// 此时pivot的位置(r和l的相遇位置)将数组分割为了两边,左半边总是不大于右半边的数字。
private static int partition(int[] arr, int left, int right) {
int pivot = arr[left];
int l = left;
int r = right;
while (l < r) {
while (l <= r && arr[l] <= pivot)
l++;
while (l <= r && arr[r] >= pivot)
r--;
if (l < r)
swap(arr, l, r);
}
arr[left] = arr[r];
arr[r] = pivot;
return r;
}
// partition中的交换元素位置函数
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 辅助函数:将int[] 打印出来
private static void arrPrint(int[] arr) {
StringBuilder str = new StringBuilder();
str.append("[");
for (int v : arr) {
str.append(v + ", ");
}
str.delete(str.length() - 2, str.length());
str.append("]");
System.out.println(str.toString());
}
}
该实例的快速排序动画演示如下:(省略了基准值分割函数partition)
基准值分割函数动画演示如下(动图有误,l应该从left开始):