内容介绍
快速排序简介
快速排序(Quicksort)是对冒泡排序的一种改进。快速排序由C. A. R. Hoare在1960年提出。快速排序算法被列为20世纪十大算法之一,这足以说明的他的作用和重要性。快速排序是程序员必须掌握的一种排序算法。
希尔排序相当于直接插入排序的升级,它们同属于插入排序类,快速排序其实就是我们前面认为最慢的冒泡排序的升级,它们都属于交换排序类。它也是通过不断比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和移动的距离,快速排序会取一个分界值,将比分界值大的记录从前面直接移动到后面,比分界值小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。
快速排序的思想
快速排序的思想:取一个分界值,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比分界值小,另外一部分的所有数据比分界值大,然后再按此方法对这两部分数据分别进行相同操作,整个排序过程可以递归进行,最终达到整个数据变成有序序列。
快速排序动画演示
快速排序分析
一般没有特殊要求排序算法都是升序排序,小的在前,大的在后。
数组由{5, 3, 1, 9, 7, 2, 8, 6} 这8个无序元素组成。
快速排序步骤:
-
取一个分界值:我们暂且拿待排序数据的最前一个元素作为分界值(枢轴)。
-
分区,low到high之间的元素分成左边小于枢轴,最右边大于枢轴。
-
分区后小于枢轴和大于枢轴的两个区域再进行分区,依次类推直到每个分区数据满足左边小于枢轴,右边大于枢轴,排序完成。
最终结果,如下图:
快速排序需要解决的两个问题:
- 分区后还需要分区可以使用递归。
- 分区时如何让小于枢轴的数据放到枢轴左边,大于枢轴的数据放到枢轴的右边。
使用两个指针(i
,j
),i
是用来找小于枢轴的数据,j
是用来找大于枢轴的数据。
i. 循环查找到需要换位置的数据,进行换位置。
ii. 当i索引的数据大于枢轴,这个数据需要换位置,记录i的值,停止查找。
iii. 当j索引的数据小于枢轴,这个数据需要换位置,记录j的值,停止查找。
iv. 如果i > j说明已经找完了,退出循环。
v. 让i和j位置的元素换位置,i++,指针向右移动继续找大于枢轴的数据,j–向左移动继续找小于枢轴的数据。过程如下动画所示:
快速排序代码编写
代码说明:
void quickSort(int[] arr)
方法:用于快速排序的方法,参数为需要排序的数组。void qSort(int[] arr, int low, int high)
方法:用于将数组指定范围的数据进行快速排序,此方法不暴露给用户使用。int partition(int[] arr, int low, int high)
方法:快速排序的分区,将low到high之间的元素分成左边小于枢轴,最右边大于枢轴,返回枢轴的位置。void swap(int[] arr, int start, int end)
方法:将arr数组start索引和end索引的元素进行交换位置。
快速排序代码如下:
public class QuickSortTest {
public static void main(String[] args) {
int[] arr = new int[] {
5, 3, 1, 9, 7, 2, 8, 6};
quickSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
public static void quickSort(int[] arr) {
qSort(arr, 0, arr.length-1);
}
// 对arr数组的[low, right]部分进行快速排序
private static void qSort(int[] arr, int low, int high) {
if (low >= high) return;
// 快速排序的分区,将low到high之间的元素分成左边小于枢轴,最右边大于枢轴
int pivot = partition(arr, low, high);
// 再次对枢轴左边和右边的数据进行分区。
qSort(arr, low, pivot - 1);
qSort(arr,pivot + 1, high);
}
// 快速排序的分区,将low到high之间的元素分成左边小于枢轴,最右边大于枢轴,返回枢轴的位置。
private static int partition(int[] arr, int low, int high) {
// 将第一个元素作为枢轴
int v = arr[low];
int i = low + 1; // arr[low+1, i) <= v; arr(j, high] >= v
int j = high;
while (true) {
// 从左边找到大于枢轴的数据
while (i <= high && arr[i] < v) {
i++;
}
// 从右边找到小于枢轴的数据
while (j >= low+1 && arr[j] > v) {
j--;
}
if (i > j) break;
swap(arr, i, j); // 交换i和j位置的元素
i++; // 左边的指针向右移动继续找大于枢轴的数据
j--; // 右边的指针向左移动继续找小于枢轴的数据
}
// 交换枢轴到j索引,保证枢轴左边的元素小于枢轴,枢轴右边元素大于枢轴。
swap(arr, low, j);
return j;
}
// 数组两个元素交换
public static void swap(int[] arr, int start, int end) {
if (start == end)
return;
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
}
快速排序代码优化1
优化枢轴的选取
我们知道快速会不断对数据进行分区,选定一个枢轴,将小于枢轴的数据放到左边,大于枢轴的数据放到右边。
前面我们在对数据进行分区时,都是以数组最前面一个元素作为枢轴,枢轴的选取不够合理。这样会存在一个问题,当数据本身近乎有序时比如数据为:{1, 2, 3, 5, 6, 7, 9, 8},分区时选择最左边的数据作为枢轴,恰好是数组最小或最大数据,导致分区时,数据都在数轴一侧会导致快速排序退化为一个O(n2)的算法。
如何选取枢轴才不会让近乎有序的数据排序退化成O(n^2)呢,我们可以看到原因是我们一直选取数组最前面的一个数据作为枢轴,因此我们可以随机选取一个元素作为数轴,这样,每次都选取到最大或最小的概率就会非常低。改进后的代码如下:
public class QuickSortTest2 {
public static void main(String[] args) {
int[] arr = new int[] {
5, 3, 1, 9,