1 快排核心
1.1 partition 问题
- 问题描述:给定一个数组 arr 和一个数 num ,将小于等于 num 的数放在左边,大于 num 的数放在右边
- 解题思路:一个指针 p 指向小于等于区最后一个元素
- 若该元素小于等于 num,小于等于区扩展,并与小于等于区最后一个元素交换,然后 i++
- 若该元素大于 num,直接 i++
- 直到遍历完整个数组为止
- 代码实现
void partition(int[] arr, int num) { int p = -1; // 小于等于区最后一个元素的指针p for (int i = 0; i < arr.length; i++) { if (arr[i] <= num) { swap(arr, i, ++p); // 扩展小于等于并交换 } } }
1.2 荷兰国旗问题
- 问题描述:给定一个数组 arr 和一个数 num ,将小于 num 的数放在左边,大于 num 的数放在右边,等于 num 的数放在中间
- 解题思路:双指针,一个指针 L 指向小于区最后一个元素,一个指针 R 指向大于区第一个元素
- 若该元素小于 num,小于区扩展,并与小于区最后一个元素交换,i++
- 若该元素等于 num,直接 i++
- 若该元素大于 num,大于区扩展,并与大于区第一个元素交换
- 直到 i 指针碰到大于区第一个元素为止
- 代码实现
void netherlandsFlag(int[] arr, int num) { int L = -1; // 指向小于区最后一个元素的指针 int R = arr.length; // 指向大于区最后一个元素的指针 int i = 0; while (i < R) { if (arr[i] < num) { swap(arr, i++, ++L); // 扩展小于区并交换,i指针指向下一个元素 } else if (arr[i] > num) { swap(arr, i, --R); // 扩展大于区并交换 } else { i++; } } }
2 快速排序
2.1 快排 1.0
- 思路:玩 partition
- 每次将数组最后一个元素作为 num,0到倒数第二个元素进行 partition
- partition 完毕后,将最后一个元素与大于区第一个元素交换
- 然后在小于等于区和大于区分别玩 partition
- 每次能保证一个等于 num 的值得位置
- 代码实现
int partition2(int[] arr, int L, int R) { int p = L - 1; // 小于等于区的指针 for (int i = L; i < R; i++) { if (arr[i] < arr[R]) { swap(arr, i, ++p); } } swap(arr,++p,R); // 小于区等于区后面一个元素就是大于区第一个元素,然后大于区第一个元素与最后一个元素交换 return p; // 返回partition后num的位置 } void quickSort1(int[] arr, int L, int R) { if (L >= R) { // 左右指针重合跳出 return; } int M = partition2(arr, L, R); quickSort1(arr, 0, M-1); // 在左边玩partition quickSort1(arr, M + 1, R); // 在右边玩partition }
2.2 快排 2.0
- 思路:玩荷兰国旗
- 每次将数组最后一个元素作为 num,0 到倒数第二个元素进行荷兰国旗
- 荷兰国旗完毕后,将最后一个元素与大于区第一个元素交换
- 然后在小于区和大于区分别玩荷兰国旗
- 每次能保证一组等于 num 的值得位置,所以比 1.0 快
- 代码实现
int[] netherlandsFlag2(int[] arr, int L, int R) { int less = L-1; int more = R; int i = L; while (i < more) { if (arr[i] < arr[R]) { swap(arr, i++, ++less); } else if (arr[i] > arr[R]) { swap(arr, i, --more); } else { i++; } } swap(arr, more, R); // 将大于区第一与最后元素交换 return new int[]{++less, more}; //第一个是等于区的开始,第二个是等于区的结束 } void quickSort2(int[] arr, int L, int R) { if (L >= R) { return; } int[] flags = netherlandsFlag2(arr, L, R); quickSort2(arr, 0, flags[0] - 1); //在小于区玩荷兰国旗 quickSort2(arr, flags[1] + 1, R); //在大于区玩荷兰国旗 }
2.3 随机快排
- 思路:在快排2.0之上,不指定元素作为num,玩荷兰国旗
- 为什么比快排2.0快?因为快排2.0和1.0都会出现全部数在左边或者右边的情况,理想情况是左右两边均等
- 代码实现
void process3(int[] arr, int L, int R) { if (L >= R) { return; } swap(arr, L + (int) (Math.random() * (R - L + 1)), R); // 随机指定一个数作为 num int[] equalArea = netherlandsFlag(arr, L, R); process3(arr, L, equalArea[0] - 1); process3(arr, equalArea[1] + 1, R); }
3 总结
- 快排就是在玩 partition 或 荷兰国旗
- 因为荷兰国旗一次能确认多组相同的数的位置,所以比 partition 要快
- 随机快排极大减少最差情况的发生,所以效率更高
- 最差情况:一次荷兰国旗后,元素全在左边,或全在右边。时间复杂度:O(n2)
- 最好情况:一次荷兰国旗后,左右两边元素个数相当。时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
- 稳定性:不稳定