快速排序以及 TopN 问题
快速排序
快速排序的划分函数
first element 划分
int Partition(std::vector<int> &data, int left, int right) {
int ret = left;
int pivot = data[left];
while (left < right) {
while (left < right && data[right] > pivot) {
--right;
}
while (left < right && data[left] <= pivot) { // 把 pivot 归到左半部分
++left;
}
if (left < right) {
swap(data[left], data[right]);
}
}
swap(data[left], data[ret]);
return left;
}
void QuickSort(std::vector<int> &data, int left, int right) {
if (right - left > 0) {
int i = Partition(data, left, right - 1);
QuickSort(data, left, i);
QuickSort(data, i + 1, right);
}
}
快速排序的时间复杂度
在理想情况下,每次 partition 都能平分 n 个元素,需要 lg(n) 次的划分,每一次划分都会遍历所有元素,所以时间复杂度为 O(nlgn),在基本有序情况下 partition 函数的时间复杂度为 O(n),划分会执行接近 n 次,所以时间复杂度为 O(n^2)。
快速排序的稳定性
[1,4,2(1),3,6,2(2),5,2(3)]
我们选择第二个 2 也就是 2(2) 为基准,一次partition后,2(3)与 4 交换了位置,得到
[1,2(3),2(1),2(2),6,3,5,4]
可见是不稳定的
如果一个数字在待排序的列表中出现三次或以上,而这个数字在列表中出现的(非首次和末次)一次被选为基准(pivot)则结果肯定是不稳定的。因此,通过更改比较时 >= 符号为 > 符号是没有意义的,因为不稳定的根源在于基准的选取。
如果要得到稳定的快速排序,必须在选取基准时避免这种情况,而且在交换元素时要小心不要打乱相同元素的相对顺序。一种可选的做法是先记录相同元素的相对位置,再进行排序。这需要额外的空间开销。
TopN 问题
由快速排序的过程可知,每次 partition 调用结束后,都能确定出一个元素的位置。所以如果使用从小到大的快速排序,那么第 k 大数,就出现在 pos = len - k 下标位置。所以有如下代码:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int res_pos = nums.size() - k;
QuickSort(nums, 0, nums.size(), res_pos);
return nums[res_pos];
}
int Partition(std::vector<int>& data, int left, int right) {
int ret = left;
int pivot = data[left];
while (left < right) {
while (left < right && data[right] > pivot) {
--right;
}
while (left < right && data[left] <= pivot) {
++left;
}
if (left < right) {
swap(data[left], data[right]);
}
}
swap(data[left], data[ret]);
return left;
}
void QuickSort(std::vector<int>& data, int left, int right, int k) {
if (right - left > 0) {
int i = Partition(data, left, right - 1);
if (i == k) {
return;
}
QuickSort(data, left, i, k);
QuickSort(data, i + 1, right, k);
}
}
};