快速排序是冒泡排序的一种改进版本,通过多次比较和交换基准值两边的数来实现排序。
时间复杂度分析:
快速排序和归并排序一样采用了分治法的设计思想。如果根据基准值,每次划分的两个子数组包含的元素数量是相同的,那么快排的时间复杂度为O(nlogn)。最坏情况下,如果根据基准值,每次划分的两个子数组其中一个总是只有一个元素,那快排的最坏情况的时间复杂度为O(n²)(给顺序数组排列,退化为冒泡排序)。因为在每一次划分的时候,都让一边只包含一个元素的情况是几乎不可能发生的,所以快排的平均时间复杂度是O(nlogn)。
空间复杂度:
快速排序在每次分割的过程中,需要 1 个空间存储基准值。而快排大概需要将数组分割logn次,所以占用空间也是 logn 个。
稳定性:
相等元素可能会因为分区而交换顺序,所以它是不稳定的算法。
应用:
快排的性能在顺序性越差的数据中表现越好,甚至可以比归并排序要好。这是因为虽然快速排序跟归并排序的平均时间复杂度都是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开始):
加入随机位置基准值。
在50万个元素的数组中进行排序,优化前:78毫秒;优化后:114毫秒
可以看到耗时反而增加了,这种优化不是我们想要的。
public class Main {
public static void main(String[] args) {
// 生成500000个数的数组,数字分布在-3000到3000范围内
int[] arr = randomArray(500000, 3000);
arrPrint(arr);
long begin = System.currentTimeMillis();
QuickSort(arr); // 优化前:78毫秒;优化后:114毫秒
long end = System.currentTimeMillis() - begin;
arrPrint(arr);
System.out.println("耗时:" + end + "毫秒");
}
public static void QuickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
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);
}
}
private static int partition(int[] arr, int left, int right) {
int l = left;
int r = right;
Random random = new Random(); // 优化
swap(arr, left, left + random.nextInt(right - left)); // 优化:加入随机位置基准值
int pivot = arr[left];
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;
}
// 辅助: 交换函数
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 sb = new StringBuilder();
sb.append("[");
for (int nums: arr) {
sb.append(nums + ", ");
}
sb.delete(sb.length() - 2, sb.length());
sb.append("]");
System.out.println(sb.toString());
}
// 辅助:随机数数组生成器
private static int[] randomArray(int size,int value) {
int[] arr = new int[size];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random()*(value + 1)) - (int)(Math.random()*value);
}
return arr;
}
}
参考:
https://blog.csdn.net/qq_28063811/article/details/93199834