快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
1. 算法步骤
-
从数列中挑出一个元素,称为 "基准"(pivot);
-
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
-
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
2. 代码实现
2.1 第一种实现代码
/**
* 实现快速排序,以最后一个数为基值
*
* @param arr 需要排序的数组
* @param begin 数组的开始位置
* @param pivot 基值下标
*/
public static void quickSort(int[] arr, int begin, int pivot) {
//判断数组长度是否大于1
if (pivot - begin <= 0) {
//如果数组长度小于等于1则返回数组
return;
} else {
//如果数组长度大于1
//先数组进行分组,比基值小的放左边,大的放右边
int middle = Division(arr, begin, pivot);
//对比基值小的数进行快速排序
quickSort(arr, begin, middle - 1);
//对比基值大的数进行快速排序
quickSort(arr, middle + 1, pivot);
}
}
/**
* 定义一个方法将数组进行分组,以最后一个数为基值,比基值小的放左边,大的放右边
*
* @param arr 需要进行分组的数组
* @param begin 数组的开始位置
* @param pivot 基值下标
* @return
*/
public static int Division(int[] arr, int begin, int pivot) {
int temp;
//左指针
int left = begin;
//右指针
int right = pivot - 1;
while (true) {
//从arr[left]开始遍历找到一个比arr[pivot]大的数
while (arr[left] < arr[pivot]) {
//比arr[pivot]小则左指针++
left++;
}
//从arr[right]开始反向遍历找到一个比arr[pivot]小的数
while (arr[right] > arr[pivot] && right > 0) {
//比arr[pivot]大则右指针--
right--;
}
//判断左右指针是否交叉
if (left >= right) {
//交叉则退出循环
break;
} else {
//未交叉则交换arr[left]和arr[right]的位置
swap(arr, left, right);
//交换位置之后左指针右移,右指针左移
left++;
right--;
}
}
//左右指针交叉之后说明已经遍历完一遍,这时把arr[pivot]与arr[left]交换位置把基值放到中间
swap(arr, left, pivot);
//这时比arr[pivot]小的全在左边,大的全在右边
//此时返回arr[pivot]的下标,它的下标为left,
// swap(arr[pivot],arr[left])这一步只是交换了它们的值,与下标无关,所以下标还是原来arr[left]的下标
return left;
}
/**
* 定义一个方法来交换数组中的两个值
*
* @param arr 数组
* @param left 第一个值
* @param right 第二个值
*/
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
2.2第二种实现代码
/**
* 实现快速排序,以第一个数为基值
*
* @param arr 需要排序的数组
* @param pivot 基值下标
* @param end 数组的结束位置
*/
public static void quickSort1(int[] arr, int pivot, int end) {
//判断数组长度是否大于1
if (end - pivot <= 0) {
//如果数组长度小于等于1则返回数组
return;
} else {
//如果数组长度大于1
//先数组进行分组,比基值小的放左边,大的放右边
int middle = Division1(arr, pivot, end);
//对比基值小的数进行快速排序
quickSort1(arr, pivot, middle - 1);
//对比基值大的数进行快速排序
quickSort1(arr, middle + 1, end);
}
}
/**
* 定义一个方法将数组进行分组,以第一个数为基值,比基值小的放左边,大的放右边
*
* @param arr 需要进行分组的数组
* @param pivot 基值下标
* @param end 数组的结束位置
* @return
*/
public static int Division1(int[] arr, int pivot, int end) {
int temp;
//左指针
int left = pivot + 1;
//右指针
int right = end;
while (true) {
//从arr[left]开始遍历找到一个比arr[pivot]大的数
while (arr[left] < arr[pivot] && left < right) {
//比arr[pivot]小则左指针++
left++;
}
//从arr[right]开始反向遍历找到一个比arr[pivot]小的数
while (arr[right] > arr[pivot] && right > pivot) {
//比arr[pivot]大则右指针--
right--;
}
//判断左右指针是否交叉
if (left >= right) {
//交叉则退出循环
break;
} else {
//未交叉则交换arr[left]和arr[right]的位置
swap(arr, left, right);
//交换位置之后左指针右移,右指针左移
left++;
right--;
}
}
//左右指针交叉之后说明已经遍历完一遍,这时把arr[pivot]与arr[left]交换位置把基值放到中间
swap(arr, right, pivot);
//这时比arr[pivot]小的全在左边,大的全在右边
//此时返回arr[pivot]的下标,它的下标为left,
// swap(arr[pivot],arr[left])这一步只是交换了它们的值,与下标无关,所以下标还是原来arr[left]的下标
return right;
}
/**
* 定义一个方法来交换数组中的两个值
*
* @param arr 数组
* @param left 第一个值
* @param right 第二个值
*/
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
2.3测试代码及测试结果展示
public static void main(String[] args) {
int[] arr1 = new int[]{3, 35, 33, 36, 42, 10, 141, 19, 27, 44, 26, 55};
quickSort1(arr1, 0, arr1.length - 1);
System.out.println(Arrays.toString(arr1));
int[] arr = new int[]{3, 35, 33, 36, 42, 10, 141, 19, 27, 44, 26, 55};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
// int[] arr = new int[100 * 100 ];
// for (int i = 0; i < arr.length; i++) {
// arr[i] = (int) (Math.random() * arr.length);
// }
// System.out.println(Arrays.toString(arr));
// int[] ints = Arrays.copyOf(arr, arr.length);
// int[] ints1 = Arrays.copyOf(arr, arr.length);
//
// long begin = System.currentTimeMillis();
// quickSort(ints, 0, arr.length - 1);
// long end = System.currentTimeMillis();
// System.out.println(end - begin);
//
// begin = System.currentTimeMillis();
// quickSort1(ints1, 0, arr.length - 1);
// end = System.currentTimeMillis();
// System.out.println(end - begin);
}
到这里快速排序就结束啦!有任何问题可以在评论区留言。