acwing 785.快速排序
这道题 闫总的模版和讨论区第一个可以看一下啊, 然后讨论区第一个当时有一个问题, 答主是这么回复我的.
记录一下:
问
do i++; while(q[i] < x); 会使得 q[l..i-1] <= x, q[i] >= x
这里 q[l..i-1] <= x 可以等于吗, 循环条件是小于叭
答曰:
主要原因是与循环不变式的<=配合,原因就变成了循环不变式为什么不能是 <
因为循环不变式是q[l..i] < x的话不能描述这个快排算法
假定循环不变式是 q[l..i] < x
交换前 q[l..i-1]<x,由于 swap 的两个元素 q[i] >= x q[j] <= x,交换之后就变成 q[l..i]<=x了,所以循环不变式是q[l..i]<x不能描述这个快排算法
所以无论是循环不变式还是中间过程的q[l..i-1], 都需要是 <=x
用算法笔记中的模板, 已经做了随机取第一个数的优化之后, 当10w个数字一样的时候, 仍然会超时, 下面给出一下原因和优化方案
算法笔记, 快速排序:
int quick_sort(int a[], int l, int r) {
int t = round(1.0 * rand()/RAND_MAX * (r - l) + l);
swap(a[l], a[t]);
int temp = a[l];
while (l < r) {
while (l < r && a[r] > temp) r--;
a[l] = a[r];
while (l < r && a[l] <= temp) l++; // 注意这里和之后的不同
a[r] = a[l];
}
a[l] = temp;
return l;
}
void quick_s(int a[], int l, int r) {
if (l < r) {
int mid = quick_sort(a, l, r);
quick_s(a, l, mid - 1);
quick_s(a, mid + 1, r);
}
}
分析:
为什么上面这个会超时呢, 我们假定a[]是全部一样的数组, 那么:
while (l < r && a[r] > temp) r--; 不会执行, r 保持为 e
然后while (l < r && a[l] < temp) l++; 执行 l 一直加到 r, 然后return一个 r
然后下一次quick_s的时候, mid是r, 这个时候只会进行quick_s(a, l, mid - 1);
那么前面一大坨, 相当于只处理了最后一个下标为r的数据, 每次处理一个, 自然承受不了
然后看到知乎有个文章
面试官:手写一个快速排序,并对其改进 (注意里面的代码有个地方是错了, 下面注释指出)
https://zhuanlan.zhihu.com/p/82671667
int quick_sort(int a[], int l, int r) {
int t = round(1.0 * rand()/RAND_MAX * (r - l) + l);
swap(a[l], a[t]);
int temp = a[l];
while (l < r) {
while (l < r && a[r] > temp) r--; // 记为①号, 这里没有等号哦 文章中有,是错的,通不过
if (l < r) a[l++] = a[r]; // 记为②号, 这里加了if判断和++
while (l < r && a[l] < temp) l++; // 记为③号, 这里没有等号哦
if (l < r) a[r--] = a[l]; // 记为④号, 这里加了if判断和--
}
a[l] = temp;
return l;
}
void quick_s(int a[], int l, int r) {
if (l < r) {
int mid = quick_sort(a, l, r);
quick_s(a, l, mid - 1);
quick_s(a, mid + 1, r);
}
}
分析:
这么优化的原因, 还是假设a[]全部相同, 一步一步走一遍
第一次while循环, r还是保持不懂, 然后这个时候 if判断之后, l会加上1
下一次循环时候就从加一之后的结果循环 , 注意这里是小于号不是小于等于号, 所以不会执行第二个while (l < r && a[l] < temp) l++;
然后进行if 判断, r赋值a[r--] = a[l]; 之后减一, 那么 r会减去一个1
然后l r应该是会一直往中间夹, 每次都能二分
然后看一下这个优化之后的做法, 对于普通的数列是怎么样子处理的:
假设 a[]是 3, 1, 2, 4, 5
- 首先 int mid = quick_sort(a, l, r);
l 为0, r为4, temp为3
进去之后, ①找到第一个小于等于3的数是2, r = 2
然后执行②, a[0]变为a[r]也就是a[2], 变为2, l加一变为1
然后执行③, l变为2
④的if不执行, 这里是精髓哦, 因为每次做了++ --操作, 所以赋值的时候需要进行if判断一下下标
l r 相遇为2, 找到mid数字是2, 然后变为temp也就是3, 也就是数组变为2 1 3 4 5
然后得到mid = 2, quick_s(a, l, mid - 1);也就是quick_s(a, 0, 1);
quick_s(a, mid + 1, r); 也就是quick_s(a, 3, 4); 依次进行
继续分析, 看一下如果不加上两个if 会变成啥样: 这也是我之前出错的地方
下面错误的哦
int quick_sort(int a[], int l, int r) {
int temp = a[l];
while (l < r) {
while (l < r && a[r] > temp) r--;
a[l++] = a[r];
while (l < r && a[l] < temp) l++;
a[r--] = a[l];
}
a[l] = temp;
return l;
}
void quick_s(int a[], int l, int r) {
if (l < r) {
int mid = quick_sort(a, l, r);
quick_s(a, l, mid - 1);
quick_s(a, mid + 1, r);
}
}
①是一样的, r到2
②中一样的, l变为1, 序列变为2 1 2 4 5
③一样的, l变为2
④ 注意咯, 这个时候不加if判断, 赋值这里没啥问题, 但是r--之后会变成1, 那return到底是返回 l 还是 r呢, 所以错了.
继续分析, 如果有一个while 带了等号会怎么样, 这里报错的用例还是10w个相同数字的, 走一遍分析
int quick_sort(int a[], int l, int r) {
int t = round(1.0 * rand()/RAND_MAX * (r - l) + l);
swap(a[l], a[t]);
int temp = a[l];
while (l < r) {
while (l < r && a[r] > temp) r--; // 记为①号,
if (l < r) a[l++] = a[r]; // 记为②号,
while (l < r && a[l] <= temp) l++; // 记为③号, 有等号哦
if (l < r) a[r--] = a[l]; // 记为④号,
}
a[l] = temp;
return l;
}
void quick_s(int a[], int l, int r) {
if (l < r) {
int mid = quick_sort(a, l, r);
quick_s(a, l, mid - 1);
quick_s(a, mid + 1, r);
}
}
假设a[] 全部一样, 那么
①执行之后, r不变
②执行
③执行, l增加到和r一样
然后l 和 r都是最后一个, return,
然后后面的 quick_s(a, l, mid - 1); 也就是 quick_s(a, l, r- 1); 还是处理了一个元素, 所以会报错
quick_s(a, r + 1, r); 不执行
还有一种方法需要了解一下 顺序遍历法
见 https://segmentfault.com/a/1190000004410119
官方题解有问题之后, 五万个2那个用例过不了
评论区有一个双路快速排序的做法, 不用三路排序
三路快速排序的一个做法
https://leetcode.cn/problems/sort-an-array/solution/by-heuristic-sahaqsh-49zq/
其实使用到了 顺序排序那个做法
class Solution {
public:
int piv(vector<int>& nums, int l, int r) {
int a = nums[l], b = nums[(l+r)/2], c = nums[r];
if((a <= b && b <= c) || (c <= b && b <= a)) {
swap(nums[r], nums[(l+r)/2]);
return b;
}
if((b < a && a < c) || (c < a && a < b)) {
swap(nums[l], nums[r]);
return a;
}
return c;
}
void qsort(vector<int>& nums, int l, int r) {
if(l >= r) return;
int pivot = piv(nums, l, r);
cout<<l<<","<<r<<","<<pivot<<endl;
int less=l;
for(int i=l; i<r; i++) {
if(nums[i] < pivot) {
swap(nums[i], nums[less]);
less++;
}
}
int more = less;
for(int i=less; i<=r; i++) {
if(nums[i] == pivot) {
swap(nums[i], nums[more]);
more++;
}
}
qsort(nums, l, less-1);
qsort(nums, more, r);
}
vector<int> sortArray(vector<int>& nums) {
qsort(nums, 0, nums.size()-1);
return nums;
}
};
另外一种三路排序 不过这个过不了五万个2那个用例 忽略
https://leetcode.cn/problems/sort-an-array/solution/san-by-parhelion-h-hq11/
class Solution {
public:
void quickSort(vector<int>& nums, int start, int end)
{
if (start >= end)
return;
int mid = partition3(nums, start, end);
quickSort(nums, start, mid - 1);
quickSort(nums, mid + 1, end);
}
int partition3(vector<int>& nums, int start, int end)
{
int pivot = nums[(rand() % (end - start + 1)) + start];
int zone0 = start - 1, ii = start, zone2 = end + 1;
while (ii < zone2)
{
if (nums[ii] < pivot)
swap(nums[++zone0], nums[ii++]);
else if (nums[ii] > pivot)
swap(nums[--zone2], nums[ii]);
else
++ii;
}
return zone0 + 1;
}
vector<int> sortArray(vector<int>& nums) {
quickSort(nums,0,nums.size()-1);
return nums;
}
};
作者:parhelion-h
链接:https://leetcode.cn/problems/sort-an-array/solution/san-by-parhelion-h-hq11/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
三路 能过
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
random.shuffle(nums)
self.quick_sort(nums, 0, len(nums)-1)
return nums
def quick_sort(self, nums, lo, hi):
# 3向切分, lo..lt < v, lt..gt = v, gt..hi > v
if lo >= hi:
return
lt, i, gt = lo, lo+1, hi
v = nums[lo]
while i <= gt:
if nums[i] < v:
nums[i], nums[lt] = nums[lt], nums[i]
i += 1
lt += 1
elif nums[i] > v:
nums[i], nums[gt] = nums[gt], nums[i]
gt -= 1
else:
i += 1
self.quick_sort(nums, lo, lt-1)
self.quick_sort(nums, gt+1, hi)
作者:ji-de-an-shi-chi-fan
链接:https://leetcode.cn/problems/sort-an-array/solution/912-by-ji-de-an-shi-chi-fan-vuvj/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
种一棵树最好的时间是十年前,其次是现在。