最近才知道快速排序还分二路快排和三路快排,二路快排就是我们平时学习的普通快排,而三路快排虽然听起来好像要高大上一点,其实想明白了也就是一回事。
先说说二路快排,也就是普通的快速排序。
排序的原理就是在待排序的数组中随机选择一个数key,把小于key的数统统放着key的左边,那么大于等于key的数都丢在右边了,得到一个新的数列:
[......] < key <= [......]
这样一来key实际上已经排到了正确的位置上了,我们只需要对左右两边剩下的数进行相同的操作,最后就能得到一个排序好的数列。
看代码:
//交换x和y的值
void swap(int &x, int &y) {
int temp=x;
x = y;
y = temp;
}
//a为数列,起点start,终点end(end为排序数组最后一位数的下标)
void FastSort(int *a, int start, int end) {
if (start >= end) {
return;
}
//Patition操作:将key放到合适的位置,使得其左边的数都不大于它,右边的数都不小于它
int key = a[start];
int j = start;
for (int i = start + 1; i <= end; i++) {
if (a[i] < key) { //当a[i]
快排函数中间那段,就是著名的Partition函数,非常优美的操作~
三路快排是为了应对数列中有大量的重复数据,为了避免对重复数据进行反复排序,将每次排列的数列分成三部分:
[......] < [key,key,...,key] < [......]
原理就是小的丢左边,大的数丢右边,剩下在中间的数就是与key相等的数了。这样处理后,再对key两边的数列进行排列,可以大大加快排列速度。来看看和二路快排代码有什么不同:
void FastSort2(int *a, int start, int end) {
if (start >= end) {
return;
}
//Partition操作:小于key的丢左边,大于key的丢右边
int key = a[start];
int i = start;
int j = start;
int k = end;
while (i <= k) {
if (a[i] < key) {
swap(a[i++], a[j++]);
}
else if (a[i] > key) {
swap(a[i], a[k--]);
}
else {
i++;
}
}
FastSort2(a, start, j);
FastSort2(a, k + 1, end);
}
这次的Partition函数依旧很美,相比前面的二路快排,三路快排在扫描的过程中将数据按小于、大于、等于分别操作,最后将数据分为三路,相较于二路快排,效率进一步提高~
快排的平均时间复杂度O[nlog(n)],最坏情况可能会达到O[n^2]。由于快排对应于乱序的数列排序速度较快,而越接近排好序的数列则排序速度越慢。为了解决这个问题,我们有时需要将数组打乱提高数列的混乱度,有助于提高排序速度。
这个实现方法很多,我也不做过多的介绍了~有兴趣可以自己实现一下。