快速排序(quickSort),一种典型的运用分治思想的算法。
核心思想是:
- 在数组中取一个元素作为关键的中间值keyVal。
- 然后把比中间值小的元素,放到中间值的左边;把比中间值大的元素,放到中间值右边。
- 在中间值keyVal的左半边和右半边两个子列表中,重复上面的操作。把一个数组的排序,拆分成多个规模更小的问题,逐一解决,分而治之,这就是分治了;
语言描述,我们很好懂。但是怎么转换成代码实现出来呢?这就是快速排序算法的关键,采用头尾两个下标往中间逼近的做法。如图所示:
主要流程如下:
a. 取第一个元素7作为中间值(取数组中的任意元素都可以);
b. 先从右边开始,下标递减,找到比中间值7小的元素,放到中间值左边;
c. 然后从左边开始,下标递增,找到比中间值7大的元素,放到中间值右边;
d. 重复b、c两步,在左右下标 l 和 r相遇的时候,就走完一遍,左边元素都比中间值小,右边都比中间值大了。
以上图的数组为例,结合图示看一下具体过程:
- 取数组第一个元素的值7作为中间值;
- 执行b步骤,先 r 下标递减,找到第一个比7小的元素3。
按上面快排思路我们应该把它放到7的左边,但是数组长度固定,没法像人的思维那样直观的放到7左边啊,怎么办呢?首先我们可能想到开辟额外的空间来存储,但是在考虑尽量节省空间的前提下,我们可以采用原地交换中间值7和当前访问值3的方法,3就到7左边了:
- 执行c步骤,l 下标递增,找到第一个比7大的元素10,按上面的方法原地交换:
- 重复b步骤,r 下标递减,找到第一个比7小的元素1,原地交换:
- 重复c步骤,l 下标递增,找到第一个比7大的元素9,原地交换:
- 重复b步骤,r下标递减,发现l和r下标相遇了(l=r=6),结束操作。
如上图最后的数组状态所示,现在已经左边的元素都比中间值7小、右边的元素都比中间值7大了。我们注意到:虽然最终中间值7在index=6的位置,但是在中间过程中,它的位置是有多次变动的,
接下来,对中间值7左边的子序列和右边的子序列分别重复上述的过程,就把对整个数组的排序分成了两个规模更小的子任务,在子序列中又会分成两个更小的子序列执行相同操作,以此类推,直到子序列只有一个或两个元素,完成交换后,因为所有局部子序列都是有序的,且左边子序列都比右边小,所以数组整体也是有序的,排序完成。
快速排序Java实现:
private static void quickSort(int[] arr, int start, int end) {
if (start >= end) {
return;
}
int keyVal = arr[start];
int left = start;
int right = end;
while (left < right) {
while (left < right && arr[right] >= keyVal) {
right--;
}
if (left < right) {
swap(arr, left, right);
}
while (left < right && arr[left] < keyVal) {
left++;
}
if (left < right) {
swap(arr, left, right);
}
}
// 通过递归调用,在比中间值小的子序列和比中间值大的子序列中,进行相同处理
quickSort(arr, start , left - 1);
quickSort(arr, left + 1, end);
}
// 交换元素
private static void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
插个题外话,在练习的时候,可以用洗牌算法,来生成一个乱序的数组:
private int[] genArray(int len) {
int[] arr = new int[len];
//初始化数组
for (int i = 0; i < len; i++) {
arr[i] = i + 1;
}
Random r = new Random(System.currentTimeMillis());
// 打乱数组元素顺序。经典的洗牌算法,保证每个元素出现在每个位置的概率相等。
for (int i = len - 1; i > 0; i--) {
int index = r.nextInt(i + 1);
int iVal = arr[i];
arr[i] = arr[index];
arr[index] = iVal;
}
System.out.println(Arrays.toString(arr));
return arr;
}