快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。
算法描述:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为“基准”(pivot)
- 分区(partition)操作:重新排序数列,所以元素比基准小的放在基准前面,所有元素比基准值大的放在基准后面(相同的任意一边)。在这个分区退出之后,该基准就处于数列的中间位置;
- 递归地(recursive)把小于基准元素的子数列和大于基准元素的子数列排序。
动画演示:
(不知道有没有人和我一样,光看图还是看不懂)
其实很简单,我们不是要按顺序吗?一种思路是把大的放一块,然后小的放另一块,这样这两块自己内部再分,就像以前的分班一样,有普通班和重点班两个班,高考550以上重点班,550以下普通班,这样最终如果要统计排名,肯定重点班自己排好序,普通班排好序后直接加在重点班最后一名后面就好了。
这里的550就是基准,当然在快速排序算法里,我们一般选数组的第一个数字为基准,也可以随机找个基准,或者下面的三数取中选个基准。基准的作用可大可小,但选个好基准能节省时间是真的。你要是选个600就是跟计算机过不去,能让计算机越快跑完越好。时间就是金钱。
选好基准之后我们怎么把基准放在合适的位置呢,即怎么保证让基准的位置刚好它的左边全是比它小的,它的右边全是比它大的?
其实就是这样一种思想,数组就像一座桥,桥的两端分别有个哨兵巡游,一个从前往后走,一个从后往前走。
前面的哨兵遇到靠前的比基准大的数字,把它放到后面,遇到靠后的比基准小的数字,把它放到前面。 那么要怎么放呢?比如说前面走的哨兵发现了一个数字 520 比 550要小,把这个数字放到哪呢,毫无疑问当然是应该把这个数字放到靠后,后面往前走的哨兵就可以起到接应的效果了,给谁不是给呢,后面哨兵的位置又是已知的,交换一下两个哨兵所在位置的数字就好了。
将基准放到合适的位置与数据的分布息息相关,依据现在学到的知识来看,我们还是很难预测基准的位置的,即使利用三数取中法我们也只能得到一个不是那么极端的基准(或者太大,或者太小), 我们普遍是希望基准是中位数的。
哨兵的位移也是有学问的,一般是一个哨兵遇到一个冲突的数字(比基准大却靠前或比基准小却靠后)而和另一个方向的哨兵交换数字后,另一个方向的哨兵再开始位移检测冲突的数字。比如靠前位置的哨兵发现一个数字,和靠后位置的哨兵交换数字,靠后位置的哨兵再开始走。这样做好像更适合基准是中间数吧,所以希望两个哨兵都是相同进度往中间走的,而不是一个走向另一个的尽头。
二者汇合时意味着什么? 意味着他们所在位置的左边都是比基准小的,他们所在位置的右边都是比基准大的,因为他们只有在检测完冲突的数据之后才能位移,现在他们已经没有冲突的数据可以检测了。那么这个位置,放上基准数字就是画龙点睛了。
然后基准两侧的区域各自重复上述步骤。直到有序。
快速排序优化
- 优化选取基准
2如果我们选取的 pivotkey 是处于整个序列的中间位置,那么我们可以将整个序列分成小数集合和大数集合了,但注意,如果 pivotkey 不是中间数。即总是固定选取第一个关键字或是其他位置的关键字,这是一种不合理的做法。
- 三数取中法: 取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数。
图片来源:https://www.cnblogs.com/chengxiao/p/6262208.html
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/12/14.
* 快速排序
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
quickSort(arr, 0, arr.length - 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) {
//获取枢纽值,并将其放在当前待处理序列末尾
dealPivot(arr, left, right);
//枢纽值被放在序列末尾
int pivot = right - 1;
//左指针
int i = left;
//右指针
int j = right - 1;
while (true) {
while (arr[++i] < arr[pivot]) {
}
while (j > left && arr[--j] > arr[pivot]) {
}
if (i < j) {
swap(arr, i, j);
} else {
break;
}
}
if (i < right) {
swap(arr, i, right - 1);
}
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
}
/**
* 处理枢纽值
*
* @param arr
* @param left
* @param right
*/
public static void dealPivot(int[] arr, int left, int right) {
int mid = (left + right) / 2;
if (arr[left] > arr[mid]) {
swap(arr, left, mid);
}
if (arr[left] > arr[right]) {
swap(arr, left, right);
}
if (arr[right] < arr[mid]) {
swap(arr, right, mid);
}
swap(arr, right - 1, mid);
}
/**
* 交换元素通用处理
*
* @param arr
* @param a
* @param b
*/
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}