代码
int partition(vector<int> &a, int s, int e) {
for (int i = s; i <= e; swap(a[s++], a[i++]))
while (a[e] < a[i]) i++;
return s - 1;
}
void quick_sort(vector<int> &a, int s, int e) {
if (s >= e) return;
int m = partition(a, s, e);
quick_sort(a, s, m - 1);
quick_sort(a, m + 1, e);
}
快速排序比较容易写错的点就两个
partition
里的边界判断。这里我把枢轴选在区间最后一个数,判断时又没带等号,所以while
循环必然会在最后一个数字上停下。不用判断越界。i==e
时顺便把最后一次交换也做了- 递归调用quick_sort时一定要
m-1
和m+1
这样可以保证长度减一,否者可能会死循环。
另一种受Linux内核源代码启发的写法
void quick_sort(vector<int> &a, int s, int e) {
if (s >= e) return;
int t = s;
int m = ({
for (int i = s; i <= e; swap(a[s++], a[i++]))
while (a[e] < a[i]) i++;
s - 1;
});
quick_sort(a, t, m - 1);
quick_sort(a, m + 1, e);
}
优化
为了优化快速排序最坏复杂度。
用了舍伍德算法(Sherwood)随机选取枢轴。
代码如下
std::random_device rd;
int partition(vector<int> &a, int s, int e) {
for (int i = s; i <= e; swap(a[s++], a[i++]))
while (a[e] < a[i]) i++;
return s - 1;
}
void quick_sort(vector<int> &a, int s, int e) {
if (s >= e) return;
swap(a[rd() % (e - s + 1) + s], a[e]);
int m = partition(a, s, e);
quick_sort(a, s, m - 1);
quick_sort(a, m + 1, e);
}
另外提一句,取模获得某范围的随机数其实不靠谱。这里为了方便直接就取模了。
举个例子,假设原来随机数的范围是0,1,2
各
1
3
\frac13
31的概率。如果你模2,那么0的概率就变成了
2
3
\frac23
32,1的概率还是
1
3
\frac13
31,就不是等概率了。
后记
上面的属于快慢指针法的快排。遭到洛谷的毒瘤数据暴打。如果数组中有很多相同元素。就会退化为
O
(
n
2
)
O(n^2)
O(n2)。
附上改进写法。
void quick_sort(int a[], int s, int e) {
if (s >= e) return;
int k = a[(s + e) / 2], i = s, j = e;
while (i <= j) {
while (a[i] < k) i++;
while (a[j] > k) j--;
if (i <= j) swap(a[i++], a[j--]);
}
quick_sort(a, s, j);
quick_sort(a, i, e);
}
有以下注意点
- 退出循环时要保证
i>j
,否则当i==j
时可能会死循环。比如i=j=s
时,会递归调用quick_sort(i, e);
即quick_sort(s, e);
。 - swap前的
if (i <= j)
不能少,否则当i>j
时会导致乱序。 if (i <= j)
也不能改成if (i < j)
,否则i==j
时会死循环。- 内部while循环条件不能带等号,否则会越界。