快速排序原理
从数组中任意选取一个元素,将这个元素称为中轴元素,然后我们将大于中轴元素的元素放在其右边,小于中轴元素的元素放在其左边,然后将中轴元素左边和右边分为两个小的数组,重复此过程,直到数组的大小为1,此时每个元素都处于有序的位置。
在下图的数组4,1,3,2,7,6,8中,将第一个元素设置为中轴元素,第二个元素设为i,最后一个元素设为j。然后让i和j从数组的两边向中间扫描。i向右遍历的过程中,如果遇到大于中轴元素的元素则停止移动,j向左遍历的过程中,如果遇到小于中轴元素的元素则停止移动(移动过程中需满足i <= j,不满足则停止移动)。若i和j停止移动时,i < j则交换i和j。
遍历完成后将中轴元素和j指向的元素交换位置,此时中轴元素处于有序位置。
对中轴元素前后两个数组重复此过程,直到数组只有一个元素
代码实现
public int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
// 获取中轴元素的下标
int mid = partition(arr, left, right);
// 对数组进行分割
arr = quickSort(arr, left, mid - 1);
arr = quickSort(arr, mid + 1, right);
}
return arr;
}
public int partition(int[] arr, int left, int right) {
// 将第一个元素设为中轴元素
int pivot = arr[left];
int i = left + 1;
int j = right;
while (true) {
// 向右遍历找到大于中轴元素的值
while (i <= j && arr[i] <= pivot) i++;
// 向左遍历找到小于中轴元素的值
while (i <= j && arr[j] >= pivot) j--;
if (i > j) break;
// 元素交换
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
arr[left] = arr[j];
// 将中轴元素置于有序位置
arr[j] = pivot;
return j;
}
性能分析
①时间复杂度分析:最好情况,如果每次分区操作,都能正好把数组分成大小接近相等的两个小数组,那么只需要分割logn次即可,此时的时间复杂度是O(nlogn)。但是如果出现极端的情况,例如数组1,3,5,6,8,此时需要进行大约n次分区操作,才能完成快速排序。在这种情况下快速排序的时间复杂度就从O(nlogn)退化为O(n²)。而平准时间复杂度,则是假设每次中轴元素等概率着落在数组的任意位置,此时的时间复杂度是O(nlogn)。
②空间复杂度:O(logn) ③原地排序 ④非稳定排序
中轴元素的随机选取
如果每次都以第一个元素或最后一个元素作为中轴元素,那么极端情况出现的可能性就会增加。为了降低极端情况出现的概率,我们可以随机选取中轴元素,而不是固定一个位置选取。中轴元素的随机选取有两个比较常用的分区算法。
1.三数取中法
从数组的首、尾、中间分别取出一个数,取这3个数的中间值作为中轴元素。这样取数据进行比较的分区方法,肯定比单纯取一个数要好。但是如果数组比较大,哪“三数取中”可能就不够了,就需要“五数取中”或“十数取中”。
2.随机法
随机从数组中选择一个元素作为中轴元素,这种方法不能保证每次中轴元素选的比较好,但是也不大可能出现每次都选的很差的情况,所以平均下来,退化到最糟糕的O(n²)的可能性也不大。