题目链接: 排序数组
快速排序求解
两个重点:
1. 选取随机数做 key, 不止固定选最左边的数做 key, 避免在数据有序时退化成 O(n^2) 的时间复杂度, 取 key 下标公式:
rand() % (end - begin + 1) + begin (加上begin偏移, 保证当前的 key 在当前的区间内)
2. 三段划分, 将区间划分为 [begin, left] [left + 1, right - 1] [right, end], 如图:
遍历进行中的区间划分, 如图:
划分出来的区间内的值具有如上特性, 这样可以避免数据中有大量重复的值的问题, 因为每次递归的区间只有左边的区间和右边的区间, 中间的区间不会递归, 所以当有大量重复的值时, 递归次数会大大减少.
划分区间的方法, 定义一个变量 i 并赋值为当前区间的 begin, 并且定义变量 left = begin - 1, right = end + 1, 然后以 i 为下标循环遍历当前区间, 循环结束条件为 i == right, 然后要明确:
[begin, left]: 都是小于key的值
[left + 1, right - 1]: 都是等于key的值
[right, end]: 都是大于key的值
根据这些条件, 得出更新区间的规则:
while(i < right)
{
if(nums[i] < key) swap(nums[++left], nums[i++]); //因为 nums[i] 是小于 key 的, 而区间 [begin, left] 里都是小于 key 的值, 所以要先让 left++, 此时 left 移动到的位置只可能是当前 i 的位置或者是等于 key 的值的位置, 用这个值和 nums[i] 交换没有一点问题, 将比 key 小的数交换后 i 再++
else if(nums[i] == key) i++; //因为区间 [left + 1, i] 里都是等于 key 的值, 既然当前值也等于 key, 那么 i 直接++就好了
else swap(nums[--right], nums[i]); //因为 nums[i] 是大于 key 的, 而区间 [right, end] 里都是大于 key 的值, 所以要先让 right--, 此时 right 移动到的位置是没有被遍历过的未知值, 用这个值和 nums[i] 交换没有一点问题, 将比 key 大的数交换后 i 一定不能++, 因为被交换过来的值是未知值, 还需要在下一轮判断后操作
}
题解代码:
class Solution
{
public:
void _sortArray(vector<int>& nums, int begin, int end)
{
if(begin >= end) return;
int left = begin - 1, right = end + 1;
int key = nums[rand() % (end - begin + 1) + begin];
int i = begin;
while(i < right)
{
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
_sortArray(nums, begin, left);
_sortArray(nums, right, end);
}
vector<int> sortArray(vector<int>& nums)
{
srand(time(nullptr));
_sortArray(nums, 0, nums.size() - 1);
return nums;
}
};