我有以下Quicksort,始终选择子序列的第一个元素作为其枢轴:
void qqsort(int array[], int start, int end) {
int i = start; // index of left-to-right scan
int k = end; // index of right-to-left scan
if (end - start >= 1) { // check that there are at least two elements to sort
int pivot = array[start]; // set the pivot as the first element in the partition
while (k > i) { // while the scan indices from left and right have not met,
while (array[i] <= pivot && i <= end && k > i) // from the left, look for the first element greater than the pivot
i++;
while (array[k] > pivot && k >= start && k >= i) // from the right, look for the first element not greater than the pivot
k--;
if (k > i) // if the left seekindex is still smaller than the right index, swap the corresponding elements
swap(array, i, k);
}
swap(array, start, k); // after the indices have crossed, swap the last element in the left partition with the pivot
qqsort(array, start, k - 1); // quicksort the left partition
qqsort(array, k + 1, end); // quicksort the right partition
} else { // if there is only one element in the partition, do not do any sorting
return;
}
}
现在您可以看到,该算法始终将第一个元素作为关键点:int pivot = array[start];
我想修改此算法以使其始终使用子序列的最后一个元素而不是第一个元素,因为我想分析两个实现的物理运行时间。
我尝试将int pivot = array[start];
行更改为int pivot = array[end];
,但是算法随后输出了未排序的序列:
//Changes: int pivot = array[end];
unsorted: {5 4 3 2 1}
*sorted*: {1 2 5 4 3}
为了测试另一个枢轴,我也尝试使用子序列的中心元素,但是算法仍然失败:
//Changes: int pivot = array[(start + end) / 2];
unsorted: {5 3 4 2 1}
*sorted*: {3 2 4 1 5}
有人可以帮助我正确理解该算法,并告诉我要成功实现此实现时始终需要选择子序列的最后一个元素作为枢轴,我需要进行哪些更改?
问题的原因
问题是您使用int k = end;
。将枢轴元素用作数组中的第一个元素时,最好使用int i = start;
,因为循环中的检查会略过它(array[i] <= pivot
)。但是,当您将最后一个元素用作枢轴时,k会在结束索引处停止,并将枢轴切换到分区左半部分的位置。您已经遇到麻烦了,因为您的支点很可能位于左分区的内部而不是边界。
[解决方案
要解决此问题,当使用最右边的元素作为枢轴时,需要设置int k = end - 1;
。您还需要更改用于将枢轴交换到左右分区之间的边界的行:
swap(array, i, end);
qqsort(array, start, i - 1);
qqsort(array, i + 1, end);
您必须为此使用i,因为我最终将位于右侧分区的最左侧元素(然后可以将其与位于最右侧元素中的枢轴交换,它将保留顺序)。最后,您需要将k >= i
更改为k > i
,同时将k减1,否则array [-1]索引错误的变化很小。这是不可能发生的,因为在这一点上我至少始终等于i + 1。
应该这样做。
侧注:
这是写得不好的速记,我不建议向它学习。它有一些多余的不必要的比较,还有一些其他的错误,我不会浪费时间列出。我建议使用Sedgewick和Bentley在this presentation中的快速排序。
我没有测试它,但是还是要检查它:
此
// after the indices have crossed,
// swap the last element in the left partition with the pivot
swap(array, start, k);
大概应该是
swap(array, end, i);
或类似的东西,如果我们选择end
作为枢轴。
Edit:这是一个有趣的分区算法,但不是standard。
嗯,pivot在分区逻辑中是固定的。该算法将第一个元素视为Head,将其余元素视为要分割的Body。完成分区后,最后一步是将头(枢轴)与左侧分区的last元素交换,以保持顺序。
我想使用另一种枢轴而不改变算法的唯一方法是:
...
if (end - start >= 1) {
// Swap the 1st element (Head) with the pivot
swap(array, start, pivot_index);
int pivot = array[start];
...
[第一个提示:如果数据是随机的,那么平均而言,选择哪个值作为枢轴并不重要。真正提高数据透视图“质量”的唯一方法是采用更多(例如3个)索引,并使用具有中值的索引。
第二个提示:如果更改枢轴值,则还需要更改枢轴索引。它没有明确命名,但是array[start]
在某一点被交换到已排序子序列的“中间”。您需要相应地修改此行。如果获取的索引不在子序列的边缘,则需要在迭代之前先将其交换到边缘。
第三条提示:您提供的代码注释过多。您应该能够真正理解此实现。
放入一个
swap(array, start, end)
初始化枢轴之前
int pivot = array[start]
#include <time.h>
#include <stdlib.h>
#include<iostream>
#include<fstream>
using namespace std;
int counter=0;
void disp(int *a,int n)
{
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
void swap(int a[],int p,int q)
{
int temp;
temp=a[p];
a[p]=a[q];
a[q]=temp;
}
int partition(int a[], int p, int start, int end)
{
swap(a,p,start);// to swap the pivot with the first element of the partition
counter+=end-start; // instead of (end-start+1)
int i=start+1;
for(int j=start+1 ; j<=end ; j++)
{
if(a[j]<a[start])
{
swap(a,j,i);
i++;
}
}
swap(a,start,i-1); // not swap(a,p,i-1) because p and start were already swaped..... this was the earlier mistake comitted
return i-1; // returning the adress of pivot
}
void quicksort(int a[],int start,int end)
{
if(start>=end)
return;
int p=end; // here we are choosing last element of the sub array as pivot
// here p is the index of the array where pivot is chosen randomly
int index=partition(a,p,start,end);
quicksort(a,start,index-1);
quicksort(a,index+1,end);
}
int main()
{
ifstream fin("data.txt");
int count=0;
int array[100000];
while(fin>>array[count])
{
count++;
}
quicksort(array,0,count-1);
/*
int a[]={32,56,34,45,23,54,78};
int n=sizeof(a)/sizeof(int);
disp(a,n);
quicksort(a,0,n-1);
disp(a,n);*/
cout<<endl<<counter;
return 0;
}
如果开始监视从数组的第一个元素到最后一个元素-1的每个元素,并在每次递归时都将最后一个元素作为枢轴,那么您将在准确的[[O(nlogn)时间获得答案。
#include<stdio.h>
void quicksort(int [], int, int);
int main()
{
int n, i = 0, a[20];
scanf("%d", &n);
while(i < n)
scanf("%d", &a[i++]);
quicksort(a, 0, n - 1);
i = 0;
while(i < n)
printf("%d", a[i++]);
}
void quicksort(int a[], int p, int r)
{
int i, j, x, temp;
if(p < r)
{
i = p;
x = a[r];
for(j = p; j < r; j++)
{
if(a[j] <= x)
{
if(a[j] <a[i])
{
temp = a[j];
a[j] = a[i];
a[i] = temp;
}
i++;
}
else
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
if(x != i)
{
temp = a[r];
a[r] = a[i];
a[i] = temp;
}
quicksort(a, p, i - 1);
quicksort(a, i + 1, r);
}
}