快速排序,就是我们选取数组里的一个基数,根据基数,我们把整个数组分为两部分,一边是大于该基数的,一边是小于该基数的。
如上图所示,我们选取6作为基数,经过一系列的排序后,6处于它在排序好的数组中应该存在的位置,两边分别是大于它和小于它的数字,然后我们再分别对两边分别递归快排,之后再对两边分别递归快排,之后全部汇总,得到一个有序的数组。但是我们有个问题,就是这个6不止一个,可能有两三个,那么这个时候就会出现这种情况:
1 2 4 6 6 6 7
这个时候,我们如果还是按照之前的,从lo和hi汇合的地方对两边子部分分别递归快排,这个时候两边的6就显得很多余,因为他们可以不用加入到快排的任务中去,只需要从三个6的两边进行排序即可。所以我们这个时候就要对快排进行改进!
普通快排:
public class QuickSort {
public static void sort(int[] arr, int low, int high) {
if (low >= high) {
return;
}
int index = arr[low];
int i = low;
int j = high;
while (i < j) {
while (i < j && arr[j] >= index) {
j--;
}
if (i < j) {
arr[i++] = arr[j];
arr[j] = index;
}
while (i < j && arr[i] <= index) {
i++;
}
if (i < j) {
arr[j--] = arr[i];
arr[i] = index;
}
}
sort(arr, low, i - 1);
sort(arr, i + 1, high);
}
}
我们定义两个指针less和more,从less负责掌管左边(比基数小的数),more负责掌管右边(比基数大的数)。
1 2 4 6 6 6 7
less L more
这时我们只需要对less和less前面的部分,more后面的部分进行排序,这样我们就不必和之前一样,因为基数重复而需要进行额外的一些判断了。但是还有一个问题就是,如果我们选取的基数,它所处的位置在排序好的数组里处于比较极端的位置(比如处于第一个,或者是最后一个),那这个时候我们的排序就有没有起到分而治之的作用,所以我们可以通过random来做到随机选取数组里的一个数来作为基数,这样我们就很难会出现上面的情况!
改进后的代码:
public class QuickSortImprove {
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
// 选取最后一个数字作为基数
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0]);
quickSort(arr, p[1] + 1, r);
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
int[] res = { less, more };
return res;
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}