快速排序是对冒泡排序的改进,可以分单向扫描法和双向扫描法两种。
首先看单向扫描的快速排序:
1. 选定数组的一个元素作为“主元”(也可以称为基准或枢轴)。之后扫描一趟数组,将小于或等于主元的元素放在主元的左边,大于主元的元素放在主元的右边。具体做法是:
a.选定数组的第一个元素作为主元。
b.定义左指针lp和右指针rp,用数组下标赋值。其中rp右侧的数据都大于主元。初始时,lp指向数组的第二个元素,rp指向数组的最后一个元素。
c.第一趟的扫描流程为:在左指针下标小于或等于右指针下标的条件下进行循环,比较是否 左指针所指元素小于或等于主元,如果是,左指针右移一位;否则,交换两指针所指元素,并将 右指针左移一位。
交换过程如下图所示(左指针lp所指元素大于主元,与右指针rp所指元素交换,且右指针左移一位):
d.循环继续,比较得到此时左指针所指元素小于主元(1 < 4),左指针右移一位。
e.再次比较得到左指针所指元素不小于主元(6 > 4),交换两指针所指元素,并将右指针左移一位。
交换过程如下图所示:
f.再次比较[lp]小于主元(2 < 4), 左指针右移一位。
g.[lp]不小于主元(7 > 4),交换两指针所指元素,并将右指针左移一位。
此时,lp == rp,仍满足左指针小于或等于右指针的条件,继续比较[lp]小于主元(3 < 4),那么左指针右移一位,此时lp > rp。
循环结束,rp右侧的数据都大于主元,且rp所指元素小于主元,此时交换rp所指元素与主元。至此第一趟快速排序完成。
2. 此时,数组以主元为界,分为两部分,分别对两部分递归实现快速排序,即再次选取新主元进行排序。
3. 上述递归结束的条件是:子数组中只有1个元素或0个元素。
单向扫描的快速排序图示
单向扫描的快速排序代码:
public class QuickSort {
// 递归实现单向扫描的快速排序
public void quickSort(int[] arr, int l, int r) {
if (l < r) {
// 进行一躺快速排序,取当前主元作为边界
int q = pv(arr, l, r);
// 分别扫描排序边界两侧
quickSort(arr, l, q - 1);
quickSort(arr, q + 1, r);
}
}
// 实现数组的一趟单向扫描的快速排序。从l扫描到r,返回当前主元位置
public int pv(int[] arr, int l, int r) {
// 主元p
int p = arr[l];
// 扫描指针:左指针lp,右指针rp
int lp = l + 1;
int rp = r;
while (lp <= rp) {
// 如果左指针所指的数小于等于主元,左指针右移一位
if (arr[lp] <= p)
lp++;
else { // 如果大于主元,交换左右指针的数据,右指针左移一位
swap(arr, lp, rp);
rp--;
}
}
swap(arr, l, rp);// 交换主元和指针所指的数据
return rp;
}
// 实现数组中两个元素的交换
public void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
再就是双向扫描的快速排序:
1. 和单向扫描法一样,双向扫描法也是选定数组的一个元素为主元,然后对于主元之外的元素,从左右两侧同时扫描。小于或等于主元的元素放在主元的左边,大于主元的元素放在主元的右边。具体做法是:
left向右扫描的过程中,如果left所指元素小于或等于主元,则left右移一位,否则停止移动;right向左扫描的过程中,如果right所指元素大于主元成立,则right左移一位,否则停止移动。当left和right都停止移动时,如果left ≤ right,则交换两指针所指元素。
a.选定数组的第一个元素为主元。
b.除主元之外的元素,选定数组的第二个元素为左侧left,数组的最后一个元素为右侧right,并且同时向右 / 左扫描。
c.left向右扫描(移动),发现当前所指元素不小于主元(5 > 4),停止移动;right向左扫描,发现当前所指元素不大于主元(1 < 4),停止移动。此时左右两侧都停止移动,并交换两侧所指元素。
d.重复c的步骤。再对[2]和[5]、[3]和[4]交换。
e.此时left ≤ right,left所指元素小于主元,right所指元素大于主元,所以left右移,right左移。
f.当前left > right,循环终止,交换right所指元素与主元。
至此第一趟排序完成。
2. 再用递归,将此时主元(right)左侧和右侧的子数组视为两个待排序的数组,再次进行排序。
双向扫描的快速排序代码:
public class DoubleQuickSort {
// 递归实现双向扫描的快速排序
public void quickSort(int[] arr, int l, int r) {
if (l < r) {
int q = pv2(arr, l, r);
quickSort(arr, l, q - 1);
quickSort(arr, q + 1, r);
}
}
// 一趟双向扫描的快速排序,返回当前边界
public int pv2(int[] arr, int l, int r) {
int p = arr[l];
int left = l + 1;
int right = r;
while (left <= right) {
while (left <= right && arr[left] <= p)
left++;
while (left <= right && arr[right] > p)
right--;
if (left < right)
swap(arr, left, right);
}
swap(arr, l, right);
return right;
}
// 交换数组中的两个元素
public void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}