我正在解决Stanford在Algorithms类中的QuickSort赋值,并使用中值规则来选择pivot元素。输入是1-10000的数字,输出是比较的数量
我的功能如下:
public static int noOfComp = 0;
public static void quick_sort(int[] a, int p, int r){
if(p<r) {
noOfComp+= r-p;
int mid = partition(a, p, r);
quick_sort(a, p, mid-1);
quick_sort(a, mid+1, r);
}
}
public static int median(int a[],int p, int r){
int firstPos = p;
int len = r-p+1;
int lastPos = r;
int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ;
int first = a[firstPos];
int middle = a[midPos];
int last = a[lastPos];
if (first <= middle) {
if (middle <= last) {
// first - middle - last
return midPos;
} else if (first <= last) {
// first - last - middle
return lastPos;
}
// last - first - middle
return firstPos;
}
if (first <= last) {
// middle - first - last
return firstPos;
} else if (middle <= last) {
// middle - last - first
return lastPos;
}
// last - middle - first
return midPos;
}
public static int partition(int[] a, int p, int r){
int chosen = median(a,p,r);
swap(a, p, chosen);
int pivot = a[p];
int i = p;
for (int j = p+1; j < a.length; j++) {
if (a[j] < pivot) {
i++;
swap(a, i, j);
}
}
swap(a, i,p);
return i;
}
//main
public static void main(String[] args) throws Throwable{
int i=0;
Scanner in = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
while(in.hasNext()){
i++;
in.next();
}
int[] a = new int[i];
i=0;
Scanner in2 = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
while(in2.hasNext()){
a[i++] = in2.nextInt();
}
quick_sort(a, 0, a.length-1);
System.out.println("Number of comparisons : " + noOfComp);
}
问题的答案似乎在128k左右,但我的算法输出132k。我已经阅读了代码次数,但无法确定错误。
答案 0 :(得分:1)
实际上,我的代码平均计数大约为132k,在随机排列的唯一数字数组上执行。我没有在算法中发现任何错误,除了下面的错误,但它并没有影响你的计数结果,假设正确的代码:
分区中的循环有一个错误的退出条件:
for (int j = p+1; j < a.length; j++) {
应该是:
for (int j = p+1; j <= r; j++) {
以下不是错误,但您可以重写
int len = r-p+1;
int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ;
为:
int midPos = p + (r-p)/2;
但是:你没有计算在函数中位数中进行的比较,通常应该这样做,否则算法无法与另一个(变体)进行公平比较。因此,每次调用 partition 会导致2或3次比较。这将平均数增加到大约148k!
Here它说:
使用随机数据透视选择对 n 元素进行排序所需的预期比较数为1.386 n.log(n)。 三个旋转中位数将其降低到≈1.188 n.log(n)。
对于 n = 10 000,1.188 n.log(n)≈158k,所以你的算法似乎比这个估计做的更少比较,至少对于 n 的特殊情况。
我确实看到了再次减少这个数字的方法。
主要想法是通过将三个检查值中的最低和最高值放在正确的分区中,从函数 median 中进行的比较中获利,因此不需要对它们进行处理进一步由函数 partition 中的循环。
举个例子,如果你有这样的数组:
5, 1, 2, 9, 3
然后中位数将比较5,2和3并选择3作为枢轴值。现在可以将该函数扩展为以正确的顺序放置三个被调查的元素,而无需进行额外的比较,以获得这个:
2, 1, 3*, 9, 5
然后,pivot元素不必交换到数组的开头,而是交换到第二个插槽,因为我们已经确定最左边的元素属于下面的分区:
2, 3*, 1, 0, 5
现在主分区循环可以专注于这个子数组,因为最后一个元素已知属于上层分区:
2, 3*, [1, 0], 5
在循环结束时,最后的交换将使用第二个元素而不是第一个元素:
2, 0, 1, 3*, 5
这将减少主循环中的比较次数为2。
在此变体中, median 函数将在数组中进行几次交换后始终返回第二个插槽的索引:
public static int median(int a[],int p, int r){
int m = p + (r-p)/2;
// actually sort the three elements:
noOfComp++;
if (a[r] < a[m]) {
swap(a, r, m);
}
if (p < m) { // more than 2 elements
noOfComp++;
if (a[m] < a[p]) {
swap(a, m, p);
noOfComp++;
if (a[r] < a[m]) {
swap(a, r, m);
}
}
// put the middle element (pivot) in second slot
swap(a, m, p+1);
}
return p+1;
}
分区将如下所示:
public static int partition(int[] a, int p, int r){
int k = median(a, p, r); // always returns p+1 as pivot's index
int i = k; // (k..i] is lower partition
for (int j = p+2; j < r; j++) { // positions p and r can be excluded
if (a[j] < a[k]) {
i++;
swap(a, i, j);
}
}
swap(a, i, k); // place pivot between partitions
return i;
}
在 quick_sort 中,比较次数将少两次:
noOfComp += r-p-2;
通过上述调整,比较次数平均从148k降至135k。
所以我担心虽然实际的比较次数已经减少了,但它仍然与128k不匹配。
当阵列变小时,我尝试使用insertion sort,但它没有带来太多改进。另一个想法是通过查看更多元素来改进对中位数的搜索,但前提是数组不是太小,因为与分区工作相比,查找数组的成本必须很小。
但是这项任务可能不允许所有这些调整。