快速排序
20世纪最伟大的算法之一------------快速排序
思路:先在无序数组中选取一个分区点(数组中的一个元素),扫描整个集合将比当前分区点小的放在分区点数值的左侧,比分区点大的元素放在分区点数值的右侧,直接将分区点数值放在正确位置=》分区点数值就放在了正确的位置=》重复上述过程,继续在左半区间和右半区间进行快速排序
快速排序的核心在于分区函数的实现:针对分区函数的实现,有N种操作
第一个分区方法
第二种方法(基于第一种方法的优化)
public static void quickSort(int[] arr) {
quickSortInternalByHell(arr,0,arr.length - 1);
}
//在数组arr[l..r]上进行快速排序
private static void quickSortInternalByHell(int[] arr, int l, int r) {
// 1.base case
if (l >= r) {
return;
}
int pivot = partitionByHell(arr,l,r);
// 继续在左半区间和右半区间继续进行快排
quickSortInternalByHell(arr,l,pivot - 1);
quickSortInternalByHell(arr,pivot + 1,r);
}
// 基于挖坑法的分区函数实现
private static int partitionByHell(int[] arr, int l, int r) {
int v = arr[l];
int i = l;
int j = r;
while (i < j) {
// 先从后向前扫描碰到第一个 < v的元素停止
while (i < j && arr[j] >= v) {
j --;
}
// 此时j指向第一个小于v的元素,填坑,填坑之后j对应的元素空出来了
arr[i] = arr[j];
// i继续从前向后扫描,碰到第一个 > v的元素停止
while (i < j && arr[i] <= v) {
i ++;
}
// i指向第一个 > v的元素,填坑,填坑之后i对应的位置空出来
arr[j] = arr[i];
}
// 此时i和j重合,填充分区点
arr[j] = v;
return j;
}
但是当数组近乎有序时快速排序可能会时间复杂度可能会变成O(n^2)
解决思路
三数取中法
public static void quickSort(int[] arr) {
quickSortInternalByHell(arr,0,arr.length - 1);
}
private static void quickSortInternalByHell(int[] arr, int l, int r) {
// 1.base case
if (r - l <= 15) {
// 优化1.小数组使用插入排序优化
insertionSortPart(arr,l,r);
return;
}
int pivot = partitionByHell(arr,l,r);
// 继续在左半区间和右半区间继续进行快排
quickSortInternalByHell(arr,l,pivot - 1);
quickSortInternalByHell(arr,pivot + 1,r);
}
private static int partitionByHell(int[] arr, int l, int r) {
int x = arr[l];
int y = arr[r];
int midIndex = l + ((r - l) >> 1);
int z = arr[midIndex];
int tempMax = Math.min(x,y);
int mid = Math.max(tempMax,z);
int v = mid;
int i = l;
int j = r;
while (i < j) {
// 先从后向前扫描碰到第一个 < v的元素停止
while (i < j && arr[j] >= v) {
j --;
}
// 此时j指向第一个小于v的元素,填坑,填坑之后j对应的元素空出来了
arr[i] = arr[j];
// i继续从前向后扫描,碰到第一个 > v的元素停止
while (i < j && arr[i] <= v) {
i ++;
}
// i指向第一个 > v的元素,填坑,填坑之后i对应的位置空出来
arr[j] = arr[i];
}
// 此时i和j重合,填充分区点
arr[j] = v;
return j;
}
随机数法
public static void quickSort(int[] arr) {
quickSortInternalByHell(arr,0,arr.length - 1);
}
private static void quickSortInternalByHell(int[] arr, int l, int r) {
// 1.base case
if (r - l <= 15) {
// 优化1.小数组使用插入排序优化
insertionSortPart(arr,l,r);
return;
}
int pivot = partitionByHell(arr,l,r);
// 继续在左半区间和右半区间继续进行快排
quickSortInternalByHell(arr,l,pivot - 1);
quickSortInternalByHell(arr,pivot + 1,r);
}
private static int partitionByHell(int[] arr, int l, int r) {
// 在[l..r) 生成一个随机数
int randomIndex = ThreadLocalRandom.current().nextInt(l,r);
swap(arr,l,randomIndex);
int v = arr[l];
int i = l;
int j = r;
while (i < j) {
// 先从后向前扫描碰到第一个 < v的元素停止
while (i < j && arr[j] >= v) {
j --;
}
// 此时j指向第一个小于v的元素,填坑,填坑之后j对应的元素空出来了
arr[i] = arr[j];
// i继续从前向后扫描,碰到第一个 > v的元素停止
while (i < j && arr[i] <= v) {
i ++;
}
// i指向第一个 > v的元素,填坑,填坑之后i对应的位置空出来
arr[j] = arr[i];
}
// 此时i和j重合,填充分区点
arr[j] = v;
return j;
}
算法导论的分区思想
public static void quickSort(int[] arr) {
quickSortInternalByHell(arr,0,arr.length - 1);
}
private static void quickSortInternalNew(int[] arr, int l, int r) {
if (r - l <= 15) {
insertionSortPart(arr,l,r);
return;
}
int p = partitionNew(arr,l,r);
quickSortInternalNew(arr,l,p - 1);
quickSortInternalNew(arr,p + 1,r);
}
private static int partitionNew(int[] arr, int l, int r) {
int randomIndex = ThreadLocalRandom.current().nextInt(l,r);
swap(arr,l,randomIndex);
// 分区元素
int v = arr[l];
// arr[l + 1..j] < v
// arr[j + 1 .. i) >= v
int j = l;
// i表示当前要扫描的元素
for (int i = l + 1; i <= r; i++) {
if (arr[i] < v) {
swap(arr,i,j + 1);
j ++;
}
}
// 扫描完整个集合,j对应最后一个小于v的元素,交换j和v
swap(arr,l,j);
return j;
}