/**
* @Date: 2020-04-13
* @Description: 双基准快速排序--选取 A[left], A[right] 两个数为主元
*/
public static void dualPivotQuickSort(int[] A, int left, int right) {
if (right - left >= 1) {
int p = minTwoNum(A[left], A[right]);
int q = maxTwoNum(A[left], A[right]);
int ℓ = left + 1;
int g = right - 1;
int k = ℓ;
while (k <= g) {
if (A[k] < p) {
A[k] = A[k] ^ A[ℓ];
A[ℓ] = A[k] ^ A[ℓ];
A[k] = A[k] ^ A[ℓ];
ℓ = ℓ + 1;
} else if (A[k] >= q) {
while (A[g] > q && k < g) {
g = g - 1;
}
A[k] = A[k] ^ A[g];
A[g] = A[k] ^ A[g];
A[k] = A[k] ^ A[g];
g--;
if (A[k] < p) {
A[k] = A[k] ^ A[ℓ];
A[ℓ] = A[k] ^ A[ℓ];
A[k] = A[k] ^ A[ℓ];
ℓ = ℓ + 1;
}
}
k++;
}
ℓ--;
g++;
A[left] = A[ℓ];
A[ℓ] = p;
A[right] = A[g];
A[g] = q;
dualPivotQuickSort(A, left, ℓ - 1);
dualPivotQuickSort(A, ℓ + 1, g - 1);
dualPivotQuickSort(A, g + 1, right);
}
}
private static int maxTwoNum(int a, int b) {
return a > b ? a : b;
}
private static int minTwoNum(int a, int b) {
return a < b ? a : b;
}
快速排序使子区间变得相对有序的关键是pivot,所以我们优化的方向也应该在于pivot,那就增加pivot的个数吧,而且我们可以发现,增加pivot的个数,对递归次数并不会有太大影响,有时甚至可以使递归次数减少。和insert sort类似的问题就是,pivot增加为几个呢?很显然,pivot的值也不能太大;记住,任何优化都是 有代价的 ,而增加pivot的代价就隐藏在每次 交换 元素的位置过程中,pivot越大,分的小区间越多,每次需要交换的次数就越大。
经典快排为什么快? 所谓快其实专业的说法是“时间复杂度”。对于排序算法来说主要看的是排序所需要的元素比较的次数
。
其实如果按照元素比较次数
来比较的话,Dual-Pivot快排元素比较次数其实比经典快排要多。
要理解上面的问题,先介绍点背景知识。我们平常很少考虑过CPU的速度
,内存的速度
,CPU和内存速度是否匹配
的问题。
其实它们是不匹配的。距统计在过去的25年里面,CPU的速度平均每年增长46%, 而内存的带宽每年只增长37%,那么经过25年的这种不均衡发展,它们之间的差距已经蛮大了。假如这种不均衡持续持续发展,有一天CPU速度再增长也不会让程序变得更快,因为CPU始终在等待内存传输数据,这就是传说中内存墙(Memory Wall)。
那么既然光比较元素比较次数
这种计算排序算法复杂度的方法已经无法客观的反映算法优劣了,那么应该如何来评价一个算法呢?作者提出了一个叫做扫描元素个数
的算法。
在这种新的算法里面,我们把对于数组里面一个元素的访问: array[i]
称为一次扫描
。但是对于同一个下标,并且对应的值也不变得话,即使访问多次我们也只算一次。而且我们不管这个访问到底是读还是写。其实这个所谓的扫描元素个数
反应的是CPU与内存之间的数据流量的大小。因为内存比较慢,统计CPU与内存之间的数据流量的大小也就把这个比较慢的内存的因素考虑进去了,因此也就比元素比较次数
更能体现算法在当下计算机里面的性能指标。
reference:https://www.jianshu.com/p/2c6f79e8ce6e